Friday, May 29, 2020

this is new

Beware of sharing this.

Here's how to check if your this is new, or when you are accidentally using a shared this.

function Abbey() {
    this.name = 'you know my name';
    console.log('Abbey() called');
    this.jude = function () {
        console.log("Abbey's jude() called");
    }
}

Abbey.prototype.great = function () {
    console.log('great');
};


function jude() {
    console.log('root jude() called');
}


function hey() {
    console.log('Hey() called');
    try {
        this.jude();
    } catch (ex) {
        console.log("I'm not running in node/browser's REPL. Can't resolve this.jude()");
        console.log(ex);
    }
}


new Abbey();
hey();
console.log('\nthis is messy\n');
Abbey(); // forgot the new keyword
hey();


console.log('\n\nnew Abbey():');
// a's this is new. not shared this
var a = new Abbey();
console.log(a);
a.great();

Output:

dev@Developers-iMac % node y.js
Abbey() called
Hey() called
I'm not running in node/browser's REPL. Can't resolve this.jude()
TypeError: this.jude is not a function
    at hey (/Users/dev/Desktop/y.js:22:14)
    at Object. (/Users/dev/Desktop/y.js:31:1)
    at Module._compile (internal/modules/cjs/loader.js:959:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:995:10)
    at Module.load (internal/modules/cjs/loader.js:815:32)
    at Function.Module._load (internal/modules/cjs/loader.js:727:14)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1047:10)
    at internal/main/run_main_module.js:17:11

this is messy

Abbey() called
Hey() called
Abbey's jude() called


new Abbey():
Abbey() called
Abbey { name: 'you know my name', jude: [Function] }
great

It's better to attach the instance's method to .prototype instead of attaching it to this. Aside from that it prevents accidentally using a shared this, it also unclutters the instance's data.

As seen from the output, the jude method become part of the data, whereas the great method doesn't.


The this can accidentally call other code when the code is run in node/browser's REPL. The following is the output of the same exact code above when it is run in REPL instead. Note that root jude is called.

Reduce this confusion

Experimented from: https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback/20279485#20279485


When reusing an instance's method from another object (see great and anotherGreat below), access it directly from the name of the function that holds the prototype, e.g., Beatles

Thursday, May 28, 2020

Make your function .apply.bind-friendly

The this.nice(); code below, though you can invoke your non-instance methods that way, does not mean you should.


Most of the time, Function.apply.bind's second parameter is null for utility functions. Utility functions are those functions that belongs to class, not to the instance of the class.

The .nice won't get invoked when using .apply.bind. Worse yet, when using function-based class (pre-ES6), this points to global this, so if there's a global nice function, it would get called instead.




Though ES6 has first-class support for class, and null will be assigned to this when invoking .apply.bind on class's static method, thus preventing miscalled/hijacked function, it's not a good practice to invoke static (non-instance) methods from a this reference.





Just call the function directly from the parent function/class:






Do note that TypeScript transpilation won't assign null to this when using Function.apply.bind against the generated class. this still receives global this when .apply's first parameter is passed with null value.

The transpilation can't prevent miscalled/hijacked function.






Note that miscalled/hijacked function happens only on node's REPL or browser's console tool. It does not happen when invoked directly via node:

Wednesday, May 27, 2020

Confuse yourself less when using .bind

.apply.bind, .call.bind

To make a shortcut for Math.max.apply(null, [5,8,6])

Use this:
var maxArray = Function.apply.bind(Math.max, null);

var maxValue = maxArray([5,8,6]);


This question was asked on stackoverflow:
Why it's important to pass null for bind as argument?


Without the null on bind:
var maxArray = Function.apply.bind(Math.max);


When maxArray([5,8,6]) is called, it is invoked like:

It's invoking the .apply on Math.max as:
Math.max.apply([5,8,6]);

The parameter [5,8,6] is assigned to this reference.

So both maxArray([5,8,6]) and Math.max.apply([5,8,6]) are basically invoking the Math.max without any parameters, and the [5,8,6] assigned to this reference.
Math.max();

We will see how .apply.bind works on our own object later and see its effect on this reference.

Both results to -Infinity:
dev@Developers-iMac % node
Welcome to Node.js v12.14.0.
Type ".help" for more information.
> var maxArray = Function.apply.bind(Math.max);
undefined
> maxArray([5,8,6]);
-Infinity
> Math.max.apply();
-Infinity
> Math.max();
-Infinity
>


We can invoke the maxArray properly like the following, but it somehow defeats the purpose of automagically passing the null on first parameter of Math.max.apply(null, [5,8,6]) expression
dev@Developers-iMac % node
Welcome to Node.js v12.14.0.
Type ".help" for more information.
> var maxArray = Function.apply.bind(Math.max);
undefined
> maxArray(null, [5,8,6]);
8


This is the result if we pass three parameters to Function.apply.bind:
dev@Developers-iMac % node
Welcome to Node.js v12.14.0.
Type ".help" for more information.
> var maxArray = Function.apply.bind(Math.max, null, [10, 40, 30]);
undefined
> maxArray()
40
> maxArray([50,60])
40
>

As for the .call, it is the same as .apply, as both them always require an object to be passed on their first parameter, which will then gets assigned to this reference, usually null is used as reference for this for utility functions. .apply is more flexible than .call, as .apply can grow its parameter, whereas .call's parameters are fixed at invocation.

Guess the output of Math.max.call(8,7,6)?

dev@Developers-iMac % node
Welcome to Node.js v12.14.0.
Type ".help" for more information.
> Math.max(8,7,6)
8
> Math.max.call(8,7,6)
7
> Math.max.apply(8,7,6)
Thrown:
TypeError: CreateListFromArrayLike called on non-object
> Math.max.apply([8,7,6])
-Infinity
> Math.max.apply([8,7,6],[2,4,3])
4
> Math.max.apply(null,[2,4,3])
4
> Math.max.call(null,8,7,6)
8

Here are other observations:




Why .apply.bind requires two parameters:




As we can see, if we don't pass an object reference to first parameter of .bind and we pass only one parameter, that passed parameter gets assigned to *this* reference, as seen on theThis property on line 7, likewise on this[0] of line 8. Then the actual parameter of the method something receives nothing (undefined).

Tuesday, May 5, 2020

flex is flexible, but it's not griddable

<div id="band">
    <div style="background-color: red">John</div>
    <div style="background-color: green">Paul</div>
    <div style="background-color: blue">George</div>
    <div style="background-color: yellow">Ringo</div>
</div>


<style>
    body {
        background-color: slategray;

        display: flex;
        justify-content: center;
        align-items: center;
    }

    #band {
        background-color: white;

        margin: 0 auto;
        border-style: solid;
        border-width: 2px;

        max-width: 740px;
        width: 100%;

        display: flex;
        /* justify-content: flex-start; */ /* flex-start is the default justify-content */
        flex-wrap: wrap;
    }

    #band > div {
        width: 200px;
        height: 200px;

        /* center all the names inside each box */
        display: flex;
        justify-content: center;
        align-items: center;
    }
</style>


Its output is:




If we change the justify-content to center:
display: flex;
justify-content: center;
flex-wrap: wrap;


Not only the first row's items are centered, the last item will be centered as well, which is not we want:



If we want to wrap extra items at leftmost and keep the whole grid of items at the center, we need to use grid instead of flex, and replace flex-wrap: wrap to repeat autofit and minmax. To wit:
display: grid;
justify-content: center;
grid-template-columns: repeat(auto-fit, minmax(200px, max-content));

The output is correct:



On the above display grid and grid-template-columns settings, if we change the justify-content to end:
display: grid;
justify-content: end; /* flex-end works as well, but we will just be confusing ourselves with that, we are using grid not flex */
grid-template-columns: repeat(auto-fit, minmax(200px, max-content));

The output is:


To contrast with flex-end of display: flex:
display: flex;
justify-content: flex-end;
flex-wrap: wrap;

The output is:


Same settings above, but with five items:



Let's use grid again with justify-content of end:
display: grid;
justify-content: end;
grid-template-columns: repeat(auto-fit, minmax(200px, max-content));

Its output:



This flex's flex-start:
display: flex;
justify-content: flex-start;
flex-wrap: wrap;

And grid's start:
display: grid;
justify-content: start;
grid-template-columns: repeat(auto-fit, minmax(200px, max-content));

Have same output:

Thursday, April 30, 2020

Don't use label display block for radio and checkbox

<div class="form-group">
  <label id="recommend-label" class="label">Would you recommend freeCodeCamp to a friend?</label>
  
    <label><input type="radio" name="recommend" value="definitely">Definitely the greatest tutorial on earth</label>
  
  
    <label><input type="radio" name="recommend" value="maybe">Maybe</label>
  
  
    <label><input type="radio" name="recommend" value="not-sure">Not sure</label>
</div>

<style>
.form-group label:not(.label) {
  display: block;
  background: lightgreen;
  margin: 2px;  
}
</style>


The problem with label display block approach is that it makes the trailing spaces after the label clickable too.



Demo: https://jsfiddle.net/qadxjghy/


To make the label's trailing spaces not clickable, enclosed the label in div. And then change the input's enclosing label display to inline-flex

<div class="form-group">
  <label id="recommend-label" class="label">Would you recommend freeCodeCamp to a friend?</label>
  
    <div>
      <label><input type="radio" name="recommend" value="definitely">Definitely the greatest tutorial on earth</label>
    </div>
  
    <div>
      <label><input type="radio" name="recommend" value="maybe">Maybe</label>
    </div>
  
    <div>
      <label><input type="radio" name="recommend" value="not-sure">Not sure</label>
    </div>

</div>

.form-group label:not(.label) {
  display: inline-flex;
  background: lightgreen;
  margin: 2px;  
}

Output:



Demo: https://jsfiddle.net/qadxjghy/1/


display: inline-block can be used as well:



inline-flex is better, so we can use align-items: baseline to align the baseline of text to the baseline of the radio/checkbox buttons:



display attribute can be removed as well, but you can't set things like margin:



Friday, March 20, 2020

Angular Service

Simpler way to make Angular service available to component, just add providedIn: 'root'

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


Then on consuming component:
@Component({
    selector: 'pm-products',
    templateUrl: './product-list.component.html'
    // no need to add provider
})
export class ProductListComponent {
    constructor(private _productService: ProductService)
}


To make it more explicit:

@Injectable()
export class ProductService {

Then on consuming component:
@Component({
    selector: 'pm-products',
    templateUrl: './product-list.component.html'
    providers: [ProductService] // explicitly add the service here
})
export class ProductListComponent {
    constructor(private _productService: ProductService)
}

Thursday, March 12, 2020

Use .filter? Sorry, no control flow analysis joy for you

function* getRoutesComponentsX(routes: Routes) {
    yield* routes
        .filter(route => route.component)
        .map(route => route.component);

    yield* routes
        .filter(route => route.children)
        .map(route => Array.from(getRoutesComponentsX(route.children!)))
        .reduce((a, b) => a.concat(b), []);
}

TypeScript won't be able to follow the flow of control when using .filter instead of if statement.

That would sometimes lead to use of non-null assertion operator.

Removing the non-null assertion operator would lead to this error:



Here's essentially same code, albeit using just if statements, no more need to use the non-null assertion operator.
function* getRoutesComponents(routes: Routes) {
    for (const route of routes) {
        if (route.component) {
            yield route.component;
        }

        if (route.children) {
            yield* getRoutesComponents(route.children);
        }
    }
}

Wednesday, March 11, 2020

Extracting components from Angular Routes

When I forgot to declare (in declarations) the components used in Angular Routes, it resulted to error: NG8002: Can't bind to 'ngModel' since it isn't a known property of 'input'


I decided to make a helper function:

function* getRoutesComponents(routes: Routes) {
    for (const route of routes) {
        if (route.component) {
            yield route.component;

            if (route.children) {
                yield* getRoutesComponents(route.children);
            }
        }
    }
}


Problem is, it gives these errors:




To enable runtime goodness, we need to disable AOT compilation, add --aot=false on angular commandline. However, disabling AOT would cause two major inconveniences.

First inconvenience, we cannot use constants as keys on objects (e.g., Angular Route) anymore. Doing so would result to example error: ERROR in Cannot read property 'loadChildren' of undefined




We can rectify the problem by avoiding the use of constants as keys:




Second inconvenience when AOT is disabled, despite fullTemplateTypeCheck is set to true, Angular won't be able to check name of the object(and properties) you are binding to ngModel if the name has a typo or misspellings. Angular will still happily build your project even if your template has full of typos or misspellings, which will result to runtime errors.



On the screenshot above, productNameBlahMeh does not exist on Product interface, yet Angular still build the project. This is due to AOT being disabled.


Those two inconveniences greatly outweighs the safety net for committing typos (or spelling mistakes). const can prevent typo errors as it enforces the single source of names in the code. It's hard to rely on developers not making typo or misspelling mistakes. fullTemplateTypeCheck only works if AOT is enabled.

Tuesday, March 10, 2020

NG8002: Can't bind to 'ngModel' since it isn't a known property of 'input'

This error will happen too even if the FormsModule is imported directly or indirectly (from shared module for example) in the feature module, if the imported component is not declared on declarations:



I followed Deborah Kurata's Angular Routing course, while I added the imported component ProductEditInfoComponent on Angular Route's component property, I forgot to add ProductEditInfoComponent on declarations property.

Adding the ProductEditInfoComponent on declarations property would solve the NG8002: Can't bind to 'ngModel' since it isn't a known property of 'input'. problem




Sunday, March 8, 2020

TypeScript non-null assertion operator

Use non-null assertion operator if and only if you can assert that it will not result to null


If the parameter name passed to paramMap.get has a typo, the paramMap.get will return null. How can the developer really assert that the expression would not return null if the developer can't guarantee that the code has no typo?
product-resolver.service.ts:
@Injectable({
    providedIn: "root"
})
export class ProductResolver implements Resolve<ProductResolved> {
    constructor(private productService: ProductService) {}

    resolve(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
    ): Observable<ProductResolved> {
        const id = +route.paramMap.get('id')!;

        // A power user error, or QA-induced error
        if (isNaN(id)) {
            const error = `Product id was not a number: ${id}`;
            console.error(error);

            // false: cancel the route
            // null: continue, the component handle the null object
            // navigate to error page: no built-in resolver mechanism for passing error
            // messages out a route resolver to the prior or activated route.
            return of({ product: null, error });
        }

        return this.productService.getProduct(id).pipe(
            map(product => ({ product })),
            catchError(error => {
                const message = `Retrieval error: ${error}`;
                return of({ product: null, error });
            })
        );
    }
}


Solution:

Since the 'id' parameter on route.paramMap.get is a parameter on Angular Routes, introduce a const for 'id'. This way, making a typo would be impossible, then we can validly assert that it would be impossible to obtain null from paramMap.get since we can only use parameter names on paramMap.get that are present on Angular Routes.


product-resolver.params.ts:
export const PRODUCT_DETAIL_ID_PARAM = "id";
export const PRODUCT_RESOLVED = "productResolved";



product.module.ts:
import {
    PRODUCT_DETAIL_ID_PARAM,
    PRODUCT_RESOLVED
} from "./product-resolver.params";

const ROUTES: Routes = [
    { path: "products", component: ProductListComponent },
    {
        path: `products/:${PRODUCT_DETAIL_ID_PARAM}`,
        resolve: { [PRODUCT_RESOLVED]: ProductResolver },
        component: ProductDetailComponent
    },
    {
        path: `products/:${PRODUCT_DETAIL_ID_PARAM}/edit`,
        resolve: { [PRODUCT_RESOLVED]: ProductResolver },
        component: ProductEditComponent
    }
];

Code smell:
product-resolver.service.ts:
const id = +route.paramMap.get('id')!;


Using literal string (non-const) parameters on paramGet on non-null assertion expression would be a code smell as it is easy to make a typo when passing literal string, the expression will return null if there is a typo, thereby asserting non-null result is at best is weak. Consider any literal string as magic strings. Magic strings can be avoided by using const.

A developer using non-null assertion operator on expression that uses magic strings/numbers is a developer in hubris: "I never make typos in my entire life, promise!"


To remove that code smell on non-null assertion expression on paramMap.get, pass parameter name that was declared from const and Angular Routes:
product-resolver.service.ts:
const id = +route.paramMap.get(PRODUCT_DETAIL_ID_PARAM)!;