MacBook, defective by design banner

title:
Put the knife down and take a green herb, dude.


descrip:

One feller's views on the state of everyday computer science & its application (and now, OTHER STUFF) who isn't rich enough to shell out for www.myfreakinfirst-andlast-name.com

Using 89% of the same design the blog had in 2001.

FOR ENTERTAINMENT PURPOSES ONLY!!!
Back-up your data and, when you bike, always wear white.

As an Amazon Associate, I earn from qualifying purchases. Affiliate links in green.

x

MarkUpDown is the best Markdown editor for professionals on Windows 10.

It includes two-pane live preview, in-app uploads to imgur for image hosting, and MultiMarkdown table support.

Features you won't find anywhere else include...

You've wasted more than $15 of your time looking for a great Markdown editor.

Stop looking. MarkUpDown is the app you're looking for.

Learn more or head over to the 'Store now!

Friday, May 20, 2022

Step 1: How I chose React

I'm working for a company that has a lot of legacy code, where "legacy" means "nothing on the client newer than AngularJS". Recently, I essentially volunteered to figure out how to stop building more technical debt by proposing a "modern" JavaScript templating engine (and associated tools) to use going forward for new feature work.

But since there's very little pure greenfield work for us in the near future -- and plenty of new features on the backlog -- my solution needed to be able to operate in a lot of different environments without causing ramp-up headaches for each new team of developers. Finding a solution to that is the real challenge I'm going to describe here.

I should note that, even in a working museum of obsolete code, the idea isn't and shouldn't be to toss the legacy code and start over, no matter how tempting that might feel. As Spolsky said years ago...

The sheer volume of bugs [in Netscape 6 on release], it seems, proves that rewriting code from scratch does not make for a better code base, it makes it worse. Old code doesnโ€™t rust, it gets better, as bugs are fixed.

Lou Montulli ['one of the 5 programming superstars who did the original version of Navigator' -Spolsky] again: โ€œI laughed heartily as I got questions from one of my former employees about FTP code the he was rewriting. It had taken 3 years of tuning to get code that could read the 60 different types of FTP servers, those 5000 lines of code may have looked ugly, but at least they worked.โ€ [emphasis mine -mfn]

In brief, you want to keep as much as your legacy code running as is realistically possible, refactoring it as your resources and priorities allow.

I've split my response into two posts.

  1. The sort of research and thinking that went into picking a solution.
  2. Code examples where I'm exhibiting the solution I've selected Is Not Wrong ยฉ 1842.

What follows is part 1...

Read more ยป

Labels: , , , , ,


posted by ruffin at 5/20/2022 03:20:00 PM
Tuesday, May 05, 2020

if you mess around long enough in Angular, you're going to see a module other than RouterModule -- that is, a module that's under your control -- with a static forRoot method. It's about this time that you look up the docs for NgModule and discover that, contrary to your expectations from RouterModule's own forRoot, forRoot isn't a part of all @NgModules. It's just something RouterModule contains whose return value can be used in a module's imports.

What the heck is going on here?

Well, the quick answer is that chances are very good it's because you're lazy loading a module. Lazy loading is when you spare the user from loading all of your code until they ask for it. And they ask for it by trying to access a route that requires it.

That's the when, but that's not the "why" we use forRoot. Why is forRoot used?

Answers are a dime a dozen on the net, and I had a hard time getting it all straight until I ran into a particularly good description in this Angular training book from Rangle.io.

Let's run through the topic with a little help from Rangle quickly.


Different modes of injection

In an example in the Rangle book, we've got an Angular app set up to use two almost identical components -- but one is eagerly loaded (and called EagerComponent) and the other is loading lazily (you got it -- LazyComponent).

Each of the components has a very simple CounterService injected into their constructors. The CounterService manages a trivial state: a counter.

@Injectable()
export class CounterService {
  counter = 0;
}

From in the Angular docs, we learn that when a module is lazy loaded, it creates its own "child" ModuleInjector since the module wasn't around to get hooked up to the original injector. The new injector is created a little like the Object.assign process. If a service was in the root injector, it'll be copied over to the child injector too. But if the child has the same service listed in its definition, the root's version will be replaced with a new one scoped to the child.

That means that, if we don't do anything to anticipate it, the CounterService, a service we expect to be a singleton, will be created twice, and its state won't be shared between the root, or parent, context (the context that includes EagerComponent) and the child (the context which contains LazyComponent).

a picture of the child

Here are the scenarios the Rangle Angular 2 training book covers. The results prove, give or take, our claim: Importing a provider in a lazy loading child module creates a second instance of the service with its own state.

Link to Rangle scenario

Lazy module providers

Lazy imports

AppModule imports

SharedModule used?

SharedModule providers

Result

First

[CounterService]

N/A

N/A

No

N/A

Error

Second

N/A

SharedModule

SharedModule

Yes

[CounterService]

Two injectors, two service instances

Third

N/A

SharedModule

SharedModule
.forRoot()

Yes

None!

Singleton injector for CounterService

To round things out, there's also a "Fourth" possibility, though it isn't any more viable possibility than the first.

  1. First: Have CounterService in lazy module only -- no way to get into the eagerly loaded component! Error!
  2. Second: Have CounterService in a shared module that provides providers everywhere it's imported. Duplicate services!
  3. Third: Have CounterService in a shared module that only provides providers via forRoot Happiness
  4. Fourth: Have CounterService in the main app module only, unshared -- no way to import into the lazy module's constructor. Error!

From duplicated to shared services

Let's look a little closer at what had to change to get our lazy loaded module's component to use the same service that was set up in the root injector's when we eagerly loaded our first component.

NOTE: If you'd like to look through what the code looks like, this medium post was nice enough to include a stackblitz with code that exactly matches the examples for the pages in the Rangle book that cover dependency injection that we linked to, above!

dilbert comic showing the importance of actual code

You can take a look at the stackblitz here, though we'll run through the high notes here.

And then I've extended that code with more StackBlitzes for different scenarios that can be found here:

If you review the table, above, there are two changes that needed to be made to go from our Second scenario to our Third, which will brings us from two CounterService instances to one singleton shared service from the root injector [with a reference to the same service in the child injector too]:

  1. You need to pull the CounterService out of the SharedModule's providers (see the "SharedModule providers" column in table) so the service isn't automatically registering with each module's import.
  2. You need to create a forRoot method to, in effect, remedy the loss of the CounterService provider from step 1 when it's imported into the root module.

That is, our root module needs the provider for the CounterService. But our lazy/child module can't know CounterService is a provider or it'll try to create one of its own.

In other words, we create two avenues to import the same SharedModule...

  1. One avenue via forRoot that gives us the ModuleWithProviders.
  2. The other that's is our conventional import, but now with the SharedModule's providers removed! It's, in effect, now a "ModuleWithOUTProviders".

Let's go to the video tape...

Here's SharedModule. Old version on the left, and new version, now with a static forRoot method, on the right...

SharedModule Second Scenario SharedModule Third Scenario
1import { NgModule } from '@angular/core'; 1import { NgModule, ModuleWithProviders } from '@angular/core';
2import { CounterService } from './counter.service'; 2import { CounterService } from './counter.service';
3 3
4@NgModule({ 4@NgModule({})
5  providers: [ CounterService ]
6})
7export class SharedModule { 5export class SharedModule {
6  static forRoot(): ModuleWithProviders {
7    return {
8      ngModule: SharedModule,
9      providers: [CounterService]
10    };
11  }
8} 12}

Note that the providers section was removed from SharedModule, but it was stealthily moved to the new forRoot method.

Aside: It's interesting to keep in mind a lesson we can learn from this answer at stackoverflow.com:

By convention, the forRoot static method both provides and configures services at the same time. It takes a service configuration object and returns a ModuleWithProviders.

Emphasis there is mine. BY CONVENTION, you use forRoot. What's important is that you're returning a ModuleWithProviders<T>. NgModule.imports takes a strange combination of types, but note that ModuleWithProviders<> is explicitly called out as one:

imports: Array<Type<any> | ModuleWithProviders<{}> | any[]>

As long as you're putting a ModuleWithProviders<T> into the imports array, NgModule doesn't care how you got it. You can use good ole MyModule.AwopBopAhLooWop() if you want to; the name of the function doesn't matter. The name is a convention only, suggested by RouterModule.forRoot which, you guessed it, also returns ModuleWithProviders<T>, where, in this case, returns RouterModule for T.


And then here are the related changes in AppModule, which, as you can see, is only that we no longer import the module directly, but import the module WITH ITS PROVIDERS, which is what ModuleWithProviders<SharedModule> gives us.

This name, ModuleWithProviders, is not a coincidence. You can think of the direct module as a "ModuleWithOUTProviders" -- which isn't really a thing, but is a good mnemonic.

AppModule Second AppModule Third
1@NgModule({ 1@NgModule({
2  imports:      [ 2  imports:      [
3    BrowserModule, 3    BrowserModule,
4    SharedModule, 4    SharedModule.forRoot(),
5    RouterModule.forRoot(routes) 5    RouterModule.forRoot(routes)
6  ], 6  ],
7  declarations: [ AppComponent, EagerComponent ], 7  declarations: [ AppComponent, EagerComponent ],
8  bootstrap:    [ AppComponent ] 8  bootstrap:    [ AppComponent ]
9}) 9})
10export class AppModule { } 10export class AppModule { }
11 11

Here, we get a ModuleWithProviders from forRoot to add to the root module's imports. So it's getting essentially what it was getting before, just after it's been laundered by the forRoot method.


And then, finally, the changes in LazyModule.

This is a trick question.

LazyModule Second LazyModule Third
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core';
2import { SharedModule } from '../shared/shared.module'; 2import { SharedModule } from '../shared/shared.module';
3import { LazyComponent }   from './lazy.component'; 3import { LazyComponent }   from './lazy.component';
4  4 
5import { Routes, RouterModule } from '@angular/router'; 5import { Routes, RouterModule } from '@angular/router';
6  6 
7const routes: Routes = [ 7const routes: Routes = [
8  { path: '', component: LazyComponent } 8  { path: '', component: LazyComponent }
9]; 9];
10  10 
11@NgModule({ 11@NgModule({
12  imports: [ 12  imports: [
13    SharedModule, 13    SharedModule,
14    RouterModule.forChild(routes) 14    RouterModule.forChild(routes)
15  ], 15  ],
16  declarations: [LazyComponent] 16  declarations: [LazyComponent]
17}) 17})
18export class LazyModule {} 18export class LazyModule {}
19  19 

Don't see any changes? That's because there aren't any! Now that there are no providers in the module, the lazy loaded module gets our "ModuleWithOUTProviders" type by default.

That is, as detailed in this StackOverflow answer:

The common pattern to achieve that is not to expose providers directly on theย @NgModuleย declaration but in a staticย forRootย function (the name is not mandatory, it's a convention)


Next Level Dependancy Injection for Children

So for some reason, this has been one of the more difficult posts I've put together, so I'm going to cut scope a bit. But the next level topics would include a few interesting follow-ons, like...

There is one way to signal that we're intentionally importing the SharedModule withOUT providers: Use forChild. RouterModule does this, for instance, and it's probably responsible for all the third-party forRoot. Why not forChild too?

The CLI also addsย RouterModule.forChild(routes)ย to feature routing modules. This way, Angular knows that the route list is only responsible for providing additional routes and is intended for feature modules. You can useย forChild()ย in multiple modules.

Theย forRoot()ย method takes care of theย globalย injector configuration for the Router. Theย forChild()ย method has no injector configuration. It uses directives such asย RouterOutletย andย RouterLink.

That does exactly what you'd expect. If forRoot is ModuleWithProviders, this is a ModuleWithProvider without any providers [sic]!

It would be fun to write two variations of the StackBlitzes, above

  1. One that used forChild and forRoot where we'd change the LazyModule trivially so that it called a new SharedModule.forChild() function instead of importing the SharedModule directly.
  2. Another that used the default SharedModule for the AppModule (the root) and a forChild for the LazyModule to see what trouble you could get in backwards. I can't honestly tell if you would or not. If the default has providers and forChild doesn't, aren't you okay? Or does the providers array in an NgModule have some extra voodoo that means only forRoot works by itself?

And just to make things more complicated, there is yet another way out of this conundrum:

Set theย providedInย property of theย @Injectable()ย toย "root"....

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class UserService {
}

So that's fun.

The bottom line, however, remains the same. If you don't want lazy-loaded module specific instances of services but want to only use the root version as a singleton everywhere, you have to remove the services from your child modules' provider array and inject them in the root module with something -- convention dictates forRoot -- that returns a ModuleWithProviders.

Get it? A module. With providers. Because in the children, you're just returning the module "without providers" when it's imported. The providers come from the root.

Programming is Hard (c) 1842.

Labels: ,


posted by ruffin at 5/05/2020 09:13:00 PM
Monday, July 29, 2019

Hey, look, a decent explanation of flattening operators in RxJS:

RxJS: Avoiding switchMap-Related Bugs

The beauty of the post is that each mapping is used within the real-world context of an online store. Which mapping is used for which buttons/interactions?

(Blockquotes are from the page linked. Example: comments are from me.)

mergeMap

For example, if the user clicks the remove buttons of the first and second items, itโ€™s possible that the removal of the second item might occur before the removal of the first. With our cart, the ordering of the removals doesnโ€™t matter, so using mergeMap instead of switchMap fixes the bug.

Example: Remove item from cart buttons. You don’t care if the second item that has its remove button clicked second but it’s removed first. mergeMap, just get ’er done.

concatMap

For example, if our shopping cart has a button for increasing an itemโ€™s quantity, itโ€™s important that the dispatched actions are handled in the correct order. Otherwise, the quantities in the frontendโ€™s cart could end up out-of-sync with the quantities in the backendโ€™s cart. With actions for which the ordering is important, concatMap should be used.

Example: If order absolutely matters, you use concatMap. Increasing the number of an item in your cart needs to go in order. You don’t want a “1 widget” to turn into “2 widgets” by way of “3 widgets”, “4 widgets” after clicking the “add” button three times.

switchMap

If our applicationโ€™s cart shows the total cost of the items plus the shipping, each change to the cartโ€™s content would see a GetCartTotal action dispatched. Using switchMap for the effect/epic that handles the GetCartTotal action would be entirely appropriate.

Example: If only the last action matters and supersedes the previous ones, use switchMap. So if you want to refresh your the price of a user’s cart automatically, you can ignore any stale replies. Say the user changed quantity and you fire off a new “get total with shipping” request. Anything sent earlier is stale. Ignore those previous requests with switchMap.

(Obviously there are other uses of switchMap that are a little more complicated, but this is one.)

exhaustMap

If our shopping cart has a refresh button and the effect/epic that handles the refresh uses switchMap, each incessant button click will abort the pending refresh.

Example: Ah, this one is clever. Contra the switchMap, you want to only listen to the FIRST of the requests with exhaustMap – wait until the original is exhausted before sending another.

  • In switchMap, you want to ignore earlier requests because older is stale.
  • In exhaustMap, everything but the first is superfluous, and those newer requests should be ignored.

Their example is a refresh button. If nothing else on the page has changed, you don’t want to field new refresh requests. There’s no extra info. Wait for the first to come back and ignore the mad button presses, our lessons from Win95 and mouse movement be darned.

Labels: , ,


posted by ruffin at 7/29/2019 08:36:00 AM
Saturday, July 07, 2018

I was watching an excellent video describing the iterations of the Angular compiler, and rabbit holed a little with hidden classes.

The most enjoyable resource I found on this (and javascript optimization in general) was from mrale.ph:

[Falling back to the runtime to bullheadedly access properties from objects out of any context from our code] is an absolutely valid way to implement property lookup, however it has one significant problem: if we pit our property lookup implementation against those used in modern JS VMs we will discover that it is far too slow.

Our interpreter isย amnesiac: every time it does a property lookup it has to execute a generic property lookup algorithm, it does not learn anything from the previous attempts and has to pay full price again and again. Thatโ€™s why performance oriented VMs implement property lookup in a different way.

What if each property access in our program was capable of learning from objects that it saw before and apply this knowledge to similar objects? Potentially that would allow us to save a lot of time by avoiding costly generic lookup algorithm and instead use a quicker one that only applies to objects of certain shape.

โ€ฆ

This optimization technique is known as Inline Caching and I have written about it before.

[emph and bracketed paraphrase mine]

It's worth a full read. And once you've got how hidden classes, polymorphism, and megamorphism works, you could probably fall into exactly the same compiler optimization steps Angular's Tobias Bosch does in his video, above.


Here's a quick bit on poly/mega/morphism from the same source, as I once again save you from googling, one resource at a time.

If we continue callingย fย with objects of different shapes its degree of polymorphism will continue to grow until it reaches a predefined threshold - maximum possible capacity for the inline cache (e.g.ย 4ย for property loads in V8) - at that point [the] cache will transition to aย megamorphicย state.
...
In V8 megamorphic ICs can still continue to cache things but instead of doing it locally they will put what they want to cache into a global hashtable. This hashtable has a fixed size and entries are simply overwritten on collisions.

It's duck typing, all the way down, until you have too many ducks, at which point we default to a home-rolled bird almanac.

Labels: , , ,


posted by ruffin at 7/07/2018 09:58:00 AM

<< Older | Newer >>


Support freedom
All posts can be accessed here:


Just the last year o' posts:

URLs I want to remember:
* Atari 2600 programming on your Mac
* joel on software (tip pt)
* Professional links: resume, github, paltry StackOverflow * Regular Expression Introduction (copy)
* The hex editor whose name I forget
* JSONLint to pretty-ify JSON
* Using CommonDialog in VB 6 * Free zip utils
* git repo mapped drive setup * Regex Tester
* Read the bits about the zone * Find column in sql server db by name
* Giant ASCII Textifier in Stick Figures (in Ivrit) * Quick intro to Javascript
* Don't [over-]sweat "micro-optimization" * Parsing str's in VB6
* .ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture); (src) * Break on a Lenovo T430: Fn+Alt+B
email if ya gotta, RSS if ya wanna RSS, (?_?), ยข, & ? if you're keypadless


Powered by Blogger etree.org Curmudgeon Gamer badge
The postings on this site are [usually] my own and do not necessarily reflect the views of any employer, past or present, or other entity.