Dynamic loading of AngularJS components

Last week, I had the opportunity to speak about dynamic loading AngularJS modules and how can you achieve that using Webpack’s require.ensure method. The code and slides for the presentation can be found here. In the presentation I’ve only described how can you dynamically register router states. The basic ideea is to have some sort of mechanism to load application modules on demand and once they’re loaded, to register those new angular components in the main application in order to use them. Unfortunately, Angular register’s all of it’s components in the configuration phase so if you try to register a new directive or service after the configuration phase has ended, the component won’t be available for Angular to use.

Let’s take this simple example:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <!-- Include your required JS files here -->
        <!-- I'm using Webpack so everything is automatically included for me -->
        <title>Angular modular test app</title>
    </head>
<body>
    <!-- Use our application directive -->
    <app></app>
</body>
</html>
/**
* Create a test app.
*/
var app = angular.module('test', []);

/**
* Create an application directive
* I'm using the new Angular 1.5 syntax for components.
*/
app.component('app', {
    controller: function(){
        this.showNewDirective = false;

        // We only show the new directive after a button press.
        this.loadDirective = function(){
        this.showNewDirective = true;
        };
    },
    // In the directive template we are using an undefined directive "new-directive".
    template: '<button ng-click="$ctrl.loadDirective()" ng-if="!$ctrl.showNewDirective">Load new directive</button>
    ' +
    '<new-directive ng-if="$ctrl.showNewDirective">New directive not loaded</new-directive>'
});

The application looks like this:
Screen Shot 2016-02-29 at 12.25.57 PM
Once we press the button we will see the default “New directive not loaded” text because our “<new-directive>” was not yet defined.
Screen Shot 2016-02-29 at 12.25.45 PM

Now let’s modify our code to register the “<new-directive>” when we press the button.

app.component('app', {
    controller: function () {
        this.showNewDirective = false;

        // We only show the new directive after a button press.
        this.loadDirective = function () {
            // Register the new directive before showing it
            app.component('newDirective', {
                controller: function () {

                },
                template: '
<h1>New directive is here</h1>
'
                });
            this.showNewDirective = true;
        };
    },
    // In the directive template we are using an undefined directive "new-directive".
    template: '<button ng-click="$ctrl.loadDirective()" ng-if="!$ctrl.showNewDirective">Load new directive</button>
    ' +
    '<new-directive ng-if="$ctrl.showNewDirective">New directive not loaded</new-directive>'
});

Once you click the button again, you would expect that the new directive is showed because we registered it before we showed it. The actual result is this:

Screen Shot 2016-02-29 at 12.25.45 PM

This happens because once the config phase has ended, angular’s component method doesn’t use the same $compileProvider to register new components. With just this little piece of code added to the “App.js” file:

app.config(function ($compileProvider) {
    // Save the original $compileProvider to use it for dynamic registering
    app.component = function(name, object) {
        $compileProvider.component(name, object);
    return (this);
    };
});

If we press the button now we should see this:
Screen Shot 2016-02-29 at 3.44.58 PM

If we want this to work for all Angular’s components we need to change our config method into this:

app.config(function ($controllerProvider, $provide, $compileProvider, $filterProvider) {
    // Register directives handler
    app.component = function(name, object) {
        $compileProvider.component(name, object);
    return (this);
    };
    // Register controller handler
    app.controller = function( name, constructor ) {
        $controllerProvider.register( name, constructor );
    return( this );
    };
    // Register services handlers
    app.service = function( name, constructor ) {
        $provide.service( name, constructor );
    return( this );
    };
    app.factory = function( name, factory ) {
        $provide.factory( name, factory );
    return( this );
    };
    // Register filters handler
    app.filter = function( name, factory ) {
        $filterProvider.register( name, factory );
    return( this );
    };
});

Now you have a way to lazy load angular modules. Please don’t make your users download 5MB of javascript if they’re only going to use 2MB. Application modules like Admin, Settings, Profile, etc. should be loaded on demand.

February 2016 – BucharestJS Meetup

what-is-webpack

On the 24th of February, the first BucharestJS Meetup of 2016 will take place. It will be a special event for me because I have the honor of opening this year’s first session with a very cool presentation about AngularJS, ES6 and Webpack. If you can join the event, don’t forget to sign up here and also join BucharestJs group on Facebook.

The event will start at 18:30 and it will take place at TechHub Bucharest.

Agenda:

  • 18:30 – Participants arrival
  •  19:00 – Intro notes
  • 19:05 – Using Webpack to write large scale Angular apps with ES6 by Daniel Popescu, Computer Scientist, Adobe Romania
  • 19:50 – JavaScript concurrency model & multi-threading by Adrian-Catalin Neatu, Senior JavaScript Developer, Everymatrix
  • 20:35 – Networking

 

See you there.

Implementing a modern JS Logger

I  my last post I’ve talked about using ES decorators in your Js code. This time, I’ll try and implement a fairly simple Js Logger using class and method decorators. The idea is to have a decorator that logs something to the console every time a method is called without having to add the logging code in the method itself.

Let’s start by creating a class that will be used to test our logger.

class Person {
    constructor(name = 'Bob', age = 28){
        this._name = name;
        this._age = age;
    }

    set name(value){
        this._name = value;
    }

    setAge(value){
        this._age = value;
    }
}

Decorators are not allowed on class constructors so if we want to log something every time a new instance of Person is created, we need to decorate the class and wrap the constructor with our custom code.

Let’s create the Log decorator.

function Log(target) {

    // Create a wrapper over original constructor
    const LoggerWrapper = function () {
        // Log something every time a new instance is created
        console.log('A new instance was created');

        // Call the original constructor
        target.apply(this, arguments);
    };

    // Copy the original prototype and constructor function to the new one.
    LoggerWrapper.prototype = Object.create(target.prototype);
    LoggerWrapper.prototype.constructor = target;

    // Return the new constructor.
    return LoggerWrapper;
}

Now if we use the Log decorator on Person class we should see something in the console every time a new instance is created.

@Log
class Person {
    constructor(name = 'Bob', age = 28) {
        this._name = name;
        this._age = age;
    }

    set name(value) {
        this._name = value;
    }

    setAge(value) {
        this._age = value;
    }
}

const firstPerson = new Person(); // A new instance was created
const secondPerson = new Person(); // A new instance was created

So far, so good but this logger is not very useful if we cannot set our custom message. In order to do that, we need to modify our Log decorator to return a function instead of the target class.

function Log(message) {
    return function(target){
        // Create a wrapper over original constructor
        const LoggerWrapper = function () {
            // Log something every time a new instance is created
            console.log(message);

            // Call the original constructor
            target.apply(this, arguments);
        };

        // Copy the original prototype and constructor function to the new one.
        LoggerWrapper.prototype = Object.create(target.prototype);
        LoggerWrapper.prototype.constructor = target;

        // Return the new constructor.
        return LoggerWrapper;
    }
}

Now we can update our Person class to use the new decorator.

@Log('A new instance was created')
class Person {
    constructor(name = 'Bob', age = 28) {
        this._name = name;
        this._age = age;
    }

    set name(value) {
        this._name = value;
    }

    setAge(value) {
        this._age = value;
    }
}

const person = new Person(); // A new instance was created

Everything works exactly like before but now we can specify what message we want to log.

Now that we have this in place, we can replace our console.log with a Logger class and we should also add a logging level parameter.

We start by creating a LogLevel class.

class LogLevel {
    static DEBUG = 0;
    static INFO = 1;
    static WARN = 2;
    static ERROR = 3;
}

A Logger class.

class Logger {
    static log(message, logLevel = LogLevel.INFO) {
        // Use different console methods based on the supplied logLevel. Default is INFO.
        switch (logLevel) {
            case LogLevel.DEBUG:
                console.log(Logger.getFormattedMessage(message));
                break;
            case LogLevel.INFO:
                console.info(Logger.getFormattedMessage(message));
                break;
            case LogLevel.WARN:
                console.warn(Logger.getFormattedMessage(message));
                break;
            case LogLevel.ERROR:
                console.error(Logger.getFormattedMessage(message));
                break;
        }
    }

    // Add the time to the message
    static getFormattedMessage(message) {
        return `[${new Date().toJSON()}] - ${message}`;
    }
}

And finally we update the Log decorator to add the logging level and use our new Logger class.

function Log(message, logLevel) {
    return function(target){
        // Create a wrapper over original constructor
        const LoggerWrapper = function () {
            // Log something every time a new instance is created
            Logger.log(message, logLevel);

            // Call the original constructor
            target.apply(this, arguments);
        };

        // Copy the original prototype and constructor function to the new one.
        LoggerWrapper.prototype = Object.create(target.prototype);
        LoggerWrapper.prototype.constructor = target;

        // Return the new constructor.
        return LoggerWrapper;
    }
}

Now the output should be something like this:

[2016-02-04T16:10:10.795Z] - A new instance was created

Much better isn’t it? The only problem with using console.log to log stuff is that the browser console will tell you that the “logging” happened in Logger.js file (were the console.log is actually called) and not your class constructor. We can easily fix this by also logging the class name.

We need to update the Logger to accept a target parameter.

class Logger {
    static log(message, logLevel = LogLevel.INFO, target) {
        // Use different console methods based on the supplied logLevel. Default is INFO.
        switch (logLevel) {
            case LogLevel.DEBUG:
                console.log(Logger.getFormattedMessage(message, target));
                break;
            case LogLevel.INFO:
                console.info(Logger.getFormattedMessage(message, target));
                break;
            case LogLevel.WARN:
                console.warn(Logger.getFormattedMessage(message, target));
                break;
            case LogLevel.ERROR:
                console.error(Logger.getFormattedMessage(message, target));
                break;
        }
    }

    // Add the time to the message
    static getFormattedMessage(message, target) {
        return `[${new Date().toJSON()}][${target.name}] - ${message}`;
    }
}

and the Log decorator to send that target.

function Log(message, logLevel) {
    return function(target){
        // Create a wrapper over original constructor
        const LoggerWrapper = function () {
            // Log something every time a new instance is created
            Logger.log(message, logLevel, this.constructor);

            // Call the original constructor
            target.apply(this, arguments);
        };

        // Copy the original prototype and constructor function to the new one.
        LoggerWrapper.prototype = Object.create(target.prototype);
        LoggerWrapper.prototype.constructor = target;

        // Return the new constructor.
        return LoggerWrapper;
    }
}

After this our output will be (notice that the class name is now present in the log):

[2016-02-04T16:23:47.321Z][Person] - A new instance was created

All good for now but what if we want to use this decorator to also log something when a class method is called? In order to do this, we need to modifyour Log decorator because a method decorator needs to return the method/property descriptor and not a constructor function.

function Log(message, logLevel) {
    return function () {
        const [target, name, descriptor] = arguments;

        if (!descriptor) {
            // Create a wrapper over original constructor
            const LoggerWrapper = function () {
                // Log something every time a new instance is created
                Logger.log(message, logLevel, this.constructor);

                // Call the original constructor
                target.apply(this, arguments);
            };

            // Copy the original prototype and constructor function to the new one.
            LoggerWrapper.prototype = Object.create(target.prototype);
            LoggerWrapper.prototype.constructor = target;

            // Return the new constructor.
            return LoggerWrapper;
        }
        else {
            // If the annotated method is not a setter or getter
            if (descriptor.value) {
                // save the original method
                const method = descriptor.value;
                // and wrap it with our custom code
                descriptor.value = function (value) {
                    Logger.log(message, logLevel, target.constructor);
                    method.call(this, value);
                };
            }
            return descriptor;
        }
    }
}

Now we can use our awesome decorator on methods also.

@Log('A new instance was created')
class Person {
    constructor(name = 'Bob', age = 28) {
        this._name = name;
        this._age = age;
    }

    set name(value) {
        this._name = value;
    }

    @Log('A new age was set')
    setAge(value) {
        this._age = value;
    }
}

const person = new Person(); // [2016-02-04T16:37:14.109Z][Person] - A new instance was created
person.setAge(20); // [2016-02-04T16:37:14.110Z][Person] - A new age was set

This decorator can be extended further to automatically log the parameters passed to the constructor/method and maybe also print the method name in the logs. This can be done easily because we can use the arguments in the wrapped functions. Do you have any other improvement ideas?

P.S: Try to update the decorator to work with setters also 😛 It needs just a small change.