Search code examples
angulartypescriptangular-clipolyfills

How to add a custom polyfill to an angular-cli project?


I need the ability to easily sort collections and I've chosen to extend the Array primitive. I realize this is considered bad practice in most cases, but this will be used only internally (not a shared lib). No matter what approach I've tried I'm not getting a working result even though it works as expected in the playground.

I've tried adding the polyfill inline in /src/polyfills.ts but that gives me a "TypeError: Attempted to assign to readonly property" in the console when I call the $sortBy method...

declare global {
  interface Array<T> {
    $sortBy(sortKey:string): T[];
  }
}

if (!Array.prototype['$sortBy']) {

  Object.defineProperty(Array.prototype, '$sortBy', {
    value: function(sortKey) {
      return this.sort( function(a, b) { // TypeError here???
        if (a[sortKey] < b[sortKey]) { return -1; }
        if (a[sortKey] > b[sortKey]) { return 1; }
        return 0;
      });
    }
  })

}

I've also tried adding a plain javascript version via npm and importing but that gives me the same type error. What's the secret???

/node_modules/my-polyfills/sortBy.js

if (!Array.prototype.$sortBy) {
  Object.defineProperties(Array.prototype, {
    '$sortBy': {
      value: function (sortKey) {
        return this.sort(function (a, b) {
          if (a[sortKey] < b[sortKey]) {
            return -1;
          }
          if (a[sortKey] > b[sortKey]) {
            return 1;
          }
          return 0;
        });
      }
    }
  });
}

.angular-cli.json

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": { ... },
  "apps": [
    {
      ...,
      "scripts": [
        "../node_modules/my-polyfills/sortBy.js",
        "../node_modules/moment/moment.js"
      ],
      ...
    }
  ],
  ...
}

Solution

  • I have an generated app on angular cli 1.0.0. While I think your issue has nothing to do with the version, I have taken your code below and appended it to src/polyfills.ts it works as expected:

    declare global {
      interface Array<T> {
        $sortBy(sortKey:string): T[];
      }
    }
    
    if (!Array.prototype['$sortBy']) {
    
      Object.defineProperty(Array.prototype, '$sortBy', {
        value: function(sortKey) {
          return this.sort( function(a, b) { // TypeError here???
            if (a[sortKey] < b[sortKey]) { return -1; }
            if (a[sortKey] > b[sortKey]) { return 1; }
            return 0;
          });
        }
      })
    }
    

    in one of my components I added:

    var a = [1,2,3];
    var b = a.$sortBy('');
    console.log(b)
    

    I did not see the error in the console and array a was printed just fine.

    I think the issue you are having is because you have the code above in your src/polyfills.ts AND you have included the same polyfill in /node_modules/my-polyfills/sortBy.js and added that to scripts section of your .angular-cli

    You should add either or, not both. I recommend the former, but in it's own file and not appended to polyfills.ts

    this error TypeError: Attempted to assign to readonly property happens when you try to assign a non-writable property to something else. By using Object.defineProperty you are making $sortBy non-writable. You defined Arrays.prototype.$sortBy THEN you try to modify that $sortBy by assigning it to a new function, hence you get the error.