Creating a Line-of-sight Map

Let’s imagine we have a game’s map defined as a matrix where 0 means a free tile and 1 means an obstacle.

[
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
]

Such map, while rendered, should look like this:

Given the map’s grid, the character’s position (defined as a Tile with row and column) and the line-of-sight range, that tells us how many tiles in distance our character is able see, we want to obtain a line-of-sight grid, that gives us the visibility of every tile. To do this we add a getLineOfSightGrid() method to our Map class.

public getLineOfSightGrid(tile: Tile, range: number): number[][] {
    var lineOfSightGrid = new Array();
    for (var n = 0; n < this.rows; n++) {
        lineOfSightGrid.push(new Array().fill(null))
    }

    var point = this.getPointByTile(tile);
    for (var r = tile.row - range; r  0) {
                        lineOfSightGrid[dstTile.row][dstTile.col] = distanceFactor * visibilityFactor;
                    }
                }
            }
        }
    }
    return lineOfSightGrid;
}

private isTileValidForLineOfSight(tile: Tile): boolean {
    return tile.row >= 0 && tile.row = 0 && tile.col < this.cols
        && this.tilesData[tile.row][tile.col] == 0;
}

private getLOSDistanceFactor(point: Point, dstPoint: Point, range: number): number {
    var distance = this.getPointsDistance(point, dstPoint) / this.tilesSize;

    if (distance - range <= 0) {
        return 1;
    } else if (distance - range  prev + curr);

    if (visibility == 0.99) {
        visibility = 1;
    }
    return visibility;
}

private getLOSVisibilityCheckPoints(point: Point, dstPoint: Point): Array {
    var distance = this.getPointsDistance(point, dstPoint);
    var vx = (dstPoint.x - point.x) / distance;
    var vy = (dstPoint.y - point.y) / distance;
    var checkPoints = [dstPoint];

    var pvx = vy;
    var pvy = -vx;
    var vlength = this.tilesSize / 4;

    checkPoints.push(new Point(dstPoint.x + pvx * vlength, dstPoint.y + pvy * vlength));
    checkPoints.push(new Point(dstPoint.x - pvx * vlength, dstPoint.y - pvy * vlength));
    return checkPoints;
}

private raytrace(tile: Tile, point: Point, dstTile: Tile, dstPoint: Point): boolean {
    var dx = (dstPoint.x - point.x);
    var dy = (dstPoint.y - point.y);
    var losTiles = new Array();

    if (dx == 0) {
        if (dy > 0) {
            for (var r = tile.row + 1; r  dstTile.row; r--) {
                losTiles.push(new Tile(r, tile.col));
            }
        }
    } else if (dy == 0) {
        if (dx > 0) {
            for (var c = tile.col + 1; c  dstTile.col; c--) {
                losTiles.push(new Tile(tile.row, c));
            }
        }
    } else {
        var nextRowStart = Math.ceil(point.y / this.tilesSize) * this.tilesSize;
        var currRowStart = Math.floor(point.y / this.tilesSize) * this.tilesSize;
        var nextColStart = Math.ceil(point.x / this.tilesSize) * this.tilesSize;
        var currColStart = Math.floor(point.x / this.tilesSize) * this.tilesSize;

        var m = dy / dx;
        var q = (dstPoint.x * point.y - point.x * dstPoint.y) / dx;

        if (dy > 0) {
            for (var y = nextRowStart; y  dstPoint.y; y -= this.tilesSize) {
                losTiles = losTiles.concat(this.getRaytraceCollidingTilesVertically(y, dy, m, q));
            }
        }
        if (dx > 0) {
            for (var x = nextColStart; x  dstPoint.x; x -= this.tilesSize) {
                losTiles = losTiles.concat(this.getRaytraceCollidingTilesHorizontally(x, dx, m, q));
            }
        }
    }

    for (var p = 0; p < losTiles.length; p++) {
        var losTile = losTiles[p];
        if (this.tilesData[losTile.row][losTile.col] == 1) {
            return false;
        }
    }
    return true;
}

private getRaytraceCollidingTilesVertically(y: number, dy: number, m: number, q: number): Array {
    var losTiles = new Array();
    var losPoint = new Point(Math.round((y - q) / m), y);
    var losTile = this.getTileByPoint(losPoint);

    if (dy < 0) {
        losTile.row--;
    }

    losTiles.push(losTile);
    if (losPoint.x / this.tilesSize == Math.floor(losPoint.x / this.tilesSize)) {
        losTiles.push(new Tile(losTile.row, losTile.col - 1));
    }
    return losTiles;
}

private getRaytraceCollidingTilesHorizontally(x: number, dx: number, m: number, q: number): Array {
    var losTiles = new Array();
    var losPoint = new Point(x, Math.round(m * x + q));
    var losTile = this.getTileByPoint(losPoint);

    if (dx < 0) {
        losTile.col--;
    }

    losTiles.push(losTile);
    if (losPoint.y / this.tilesSize == Math.floor(losPoint.y / this.tilesSize)) {
        losTiles.push(new Tile(losTile.row - 1, losTile.col));
    }
    return losTiles;
}

private getPointsDistance(point: Point, dstPoint: Point): number {
    var dx = (dstPoint.x - point.x);
    var dy = (dstPoint.y - point.y);
    return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
}

where:

public getTileByPoint(point: Point): Tile {
    return {
        row: Math.floor(point.y / this.tilesSize),
        col: Math.floor(point.x / this.tilesSize)
    }
}

public getPointByTile(tile: Tile): Point {
    return {
        x: (tile.col + 0.5) * this.tilesSize,
        y: (tile.row + 0.5) * this.tilesSize
    };
}

For each tile we are combining 2 factors:

  • distanceFactor: which tells us whether a tile is close enough to be seen by the character
  • visibilityFactor: which tells us whether a tile is covered by some obstacle along the line of sight

The product of these 2 factors is giving us a line-of-sight factor (between 0 and 1) that we use to know how good is the character visibility of a certain tile. This are the values we’ll write in the line-of-sight grid.


Distance Factor

The distance factor is the easiest to calculate, we are doing this in the getLOSDistanceFactor() method. We are basically calculating the distance between our character’s tile and the destination tile (by considering their central points) and we are returning an arbitrary value depending on the result:

  • If the distance in within the character’s line-of-sight range the factor is 1, which means that the tile is completely visible
  • If the distance is within 1/4 of a tile size outside the character’s line-of-sight range the factor is 0.66, which means that the tile is almost completely visible
  • If the distance is within 1/2 of a tile size outside the character’s line-of-sight range the factor is 0.33, which means that the tile is almost completely not visible
  • Everything further than that is not visible, so the factor is 0

Here is an example of output given range = 6:


Visibility Factor

This is much more complex to obtain. It relies on the idea of casting a ray from the character’s tile to the destination tile and checking whether the ray collides with some obstacle. We are doing this in the getLOSVisibilityFactor() method.

Since we want to have something more precise that a simple boolean result (visible/not visible), something capable to give us an idea of how much visibility we have, we decided to cast not just 1, but 3 rays (showed as the red lines in the image below), starting from the character tile’s central point to the points calculated in the getLOSVisibilityCheckPoints() methods:

For each of the rays we call the raytrace() method, which returns if the ray is colliding with some obstacle or not. For each non-colliding ray we associate a score of 0.33, rounding the result to 1 if they are all passing. In this way the possible values for the visibility factor are 1, 0.66, 0.33 and 0: the same we have for the visibility factor.

Here is an example of output:

And here is an example of the final result, with both distance factor (range = 6) and visibility factor:

Of course, given that we already know the distance factor’s range, we won’t even calculate the visibility factor (which requires a heavier computation) for anything further than that.


The Ray-Tracing logic

Since this algorithm’s purpose is to return whether our ray is colliding or not with some obstacle, we define an array named ”losTiles”, where we’ll store the tiles our ray is crossing. After populating that array we’ll be able to return the result by just checking if it contains an obstacle or not.

In order to populate it, the algorithm uses different strategies: in case the origin and the destination’s tiles are in the same column or in the same row we just pick all the tiles between them. Otherwise things get more complicated and we must go with the full raytracing computation, explained below.

The idea is that, since we are working on a 2D grid, we don’t need to actually check for every point in our ray, but only for the points where we enter a new row or column. This looks easy to do, but we have to ensure that our algorithm is symmetric, so that given 4 mirrored scenarios it will return a perfectly mirrored result.

Let’s have a look to this example:

In both cases our algorithm is evaluating what tile we’ll cross when entering the yellow column from the blue one. The point to check is the intersection between the ray’s equation (for which we calculated the m and q coefficients) and the grey line representing the yellow column’s boundary.

The case on the left is quite easy: the grey line is where the yellow column begins, so its equation is: x = 200. Given that, we can calculate our point as (200, 150), which belongs to tile1.

The case on the right is instead more complicated: the grey line is where the yellow column ends, but we don’t have a precise value for that. We only know that the blue column begins at x = 200, so the yellow columns would theoretically end at 199.999…9. Independently on what precision we use to represent this number it will always be a finite one, so intrinsically inferior to the precision we had in the case on the left. The consequence is that we can’t ensure that our algorithm is symmetric.

We can avoid this problem if we only rely on where columns (and rows) begin. In this case, instead of placing the grey line where the yellow column ends, we can place it where the blue column begins. Doing so we can calculate our point as (200, 150), which belongs to tile2, with the same precision as in the case on the left. Of course tile2 belongs to the blue column, but we know that in the yellow one we’ll cross the tile on its left, so tile3.

Here is a more comprehensive example of how it works for both columns and rows:


Handling special cases

The explained method has still some corner case we need to cake care of. Indeed, without any additional check, if a ray passes exactly at an obstacle’s corner, is likely that a collision won’t be detected:

To avoid this problem, in case the calculated coordinate happens to be on a corner, the algorithm also takes the adjacent tile.

  • If given the y, our calculated x is exactly at a column’s begin, we also consider the tile in the same row and previous column.
  • If given the x, our calculated y is exactly at a row’s begin, we also consider the tile in the same column and previous row.
Advertisements

Creating a Movement Map with Dijkstra

Let’s imagine we have a game’s map defined as a matrix where 0 means a free tile and 1 means an obstacle.

[
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
]

Such map, while rendered, should look like this:

Given the map’s grid, the character’s position (defined as a Tile with row and column) and the movement range, that tells us how many tiles our character is able to move in a turn, we want to obtain a movement grid, that gives us the reachability of every tile. To do this we add a getMovementGrid() method to our Map class.

public getMovementGrid(tile: Tile, movement: number): number[][] {
    var movementGrid = new Array<Array>();
    for (var n = 0; n < this.rows; n++) {
        movementGrid.push(new Array().fill(null))
    }

    this.fillMovementGrid(movementGrid, tile, movement, 0);
    return movementGrid;
}

private fillMovementGrid(movementGrid: number[][], tile: Tile, movement: number, moved: number) {
    movementGrid[tile.row][tile.col] = moved;

    if (moved++  moved)) {
        this.fillMovementGrid(movementGrid, tile, movement, moved);
    }
}

private tryMoveOnDiagonalTile(movementGrid: number[][], tile: Tile, movement: number, moved: number,
    adjacentTiles: Tile[]) {

    if (adjacentTiles.every((tile) => this.isTileValidForMovement(tile))) {
        this.tryMoveOnAdjacentTile(movementGrid, tile, movement, moved);
    }
}

private isTileValidForMovement(tile: Tile): boolean {
    return tile.row >= 0 && tile.row = 0 && tile.col < this.cols
        && this.tilesData[tile.row][tile.col] == 0;
}

This approach uses the Dijkstra's algorithm by pretending that the map's grid is a graph where only the free tiles are considered as nodes. We also connected the tiles in a diagonal way, but only if the 2 adjacent tiles are both free (for instance we can move diagonally to NW only if both N and W are free).

The tryMoveOnAdjacentTile() and tryMoveOnDiagonalTile() methods are used to attempt to move in every possible direction, whether possible, given the current recursion's position.

The resulting grid contains null for every tile that isn't possible to reach. For the tiles we can reach the grid contains how long the required movement needs to be in order to reach them.

Here is an example of output given movement = 3:

Sails JS tasks configuration: defining a task to compile TS

The main part of the configuration happens in the “/tasks/register/compileAssets.js” file, where we define the compilation pipeline for our application. This compilation pipeline is then executed as a step in higher level pipelines, for instance where the whole build process is defined for a specific environment (“/tasks/register/build.js” for dev, “/tasks/register/buildProd.js” for prod).

Every step in those pipelines is defined as “{taskName}:{taskArgument}”, where the taskName is a Grunt module, defined in a file with the same name within the “/tasks/config/” folder, and the taskArgument defines what configuration we want to refer from it.

We can use all the pre-installed Grunt modules coming with Sails or we can install new ones from NPM. All of them can be referred in our compileAssets pipeline.


Example: adding a new task to compile TS

As first step we need to install the “typescript” NPM dependency, to use the TS compiler, and the “grunt-exec” Grunt module, in order to execute it within our pipeline.

Then we can create a “compileTs” configuration for “grunt-exec” to invoke, where we call the TS compiler. We put it into a new “/tasks/config/exec.js”:

module.exports = function (grunt) {

    grunt.config.set('exec', {
        compileTs: {
            cmd: 'tsc'
        }
    });

    grunt.loadNpmTasks('grunt-exec');
};

As we can see our command is just “tsc”, which will run the TS compiler with no additional configuration. This is because, at start, it will search for a “tsconfig.json” file in the application’s root folder, and is much easier to have our configuration there.

Here is an example of a “tsconfig.json” file, where we configure our compiler to get all the “.ts” files within our “apps” folder and to compile them into the “.tmp/public/js/apps” folder. Given that the “.tmp/public” folder is the output of our build (all the “copy” tasks just move all the necessary assets to there) we don’t need to add a “copy” task to copy them in the right place after the compilation.

{
    "compilerOptions": {
        "target": "es6",
        "module": "commonjs",
        "moduleResolution": "node",
        "sourceMap": false,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "removeComments": true,
        "noImplicitAny": false,
        "noEmitOnError": true,
        "outDir": ".tmp/public/js/apps",
        "rootDir": "apps"
    },
    "include": [
        "apps/**/*.ts"
    ]
}

Now that we created our task we just need to insert it into the compileAssets pipeline:

module.exports = function(grunt) {
    grunt.registerTask('compileAssets', [
        'clean:dev',
        'jst:dev',
        'less:dev',
        'copy:dependencies',
        'copy:dev',
        'exec:compileTs',
        'coffee:dev'
    ]);
};

Creating a Signup/Login authentication with Sails and Passport

In this example we want to implement a simple signup/login authentication system using Sails JS and Passport, with no ORM support. In order to crate this system we need to add the following dependencies:

  • bcrypt
  • passport
  • passport-local
  • mysql

The example is based on this post: http://iliketomatoes.com/implement-passport-js-authentication-with-sails-js-0-10-2/


Authentication Controller

As first thing we create an “authController” to handle signup, login and logout actions. Here we’ll use BCrypt to generate a secure hash for our passwords and Passport to handle the authentication. We’ll also use MySql to directly access the database without using an ORM layer. Apart for that we’ll keep logging in and out our application with the standard Sails’ methods req.logIn() and req.logout().

var passport = require('passport');
var mysql = require('mysql');
var bcrypt = require('bcrypt');

module.exports = {
    _config: {
        actions: false,
        shortcuts: false,
        rest: false
    },

    signup: function (req, res) {
        bcrypt.genSalt(10, function (err, salt) {
            bcrypt.hash(req.body.password, salt, function (err, hash) {
                if (!err) {
                    var user = {
                        email: req.body.email,
                        password: hash
                    };
                    var connection = mysql.createConnection(sails.config.connections.mysql);
                    connection.query('insert into users set ?', user, function (err, result) {
                        connection.end();
                        if (!err) {
                            user.id = result.insertId;
                            req.logIn(user, function (err) {
                                if (!err) {
                                    return res.redirect("/");
                                } else {
                                    sails.log.error(err);
                                    return res.redirect("/signup");
                                }
                            });
                        } else {
                            sails.log.error(err);
                            return res.redirect("/signup");
                        }
                    });
                } else {
                    sails.log.error(err);
                    return res.redirect("/signup");
                }
            });
        });
    },

    login: function (req, res) {
        passport.authenticate('local', function (err, user, info) {
            if (!err && !!user) {
                req.logIn(user, function (err) {
                    if (!err) {
                        return res.redirect("/");
                    } else {
                        sails.log.error(err);
                        return res.redirect("/login");
                    }
                });
            } else {
                sails.log.error({
                    err: err,
                    user: user,
                    info: info
                });
                return res.redirect("/login");
            }
        })(req, res);
    },

    logout: function (req, res) {
        req.logout();
        return res.redirect('/');
    }
};

Here is a description of what we are doing in each method:

  • signup
    We call the BCrypt’s genSalt() and hash() methods to generate a secure hash for our password. Then we connect to the database and store the user’s name and hash. In case of success we call the req.logIn() Sails method to login with the newly created user, otherwise we redirect back to the signup page.
  • login
    We call the Passport.authenticate() method, that checks the authentication and then invoke a callback where we can handle the result. In case an existing user has been matched by passport we call the req.logIn() Sails method to login with the matched user, otherwise we redirect back to the login page.
  • logout
    We simply call the req.logout() Sails method.

Home Controller

We create a simple controller whose actions can’t be accessed without authentication to test our system:

module.exports = {
    index: function (req, res) {
        return res.view();
    }
};

Login and Signup views

“/login” view:

<h1>Login</h1>

    
    
    

“/signup” view:

<h1>Signup</h1>

    
    
    

“/home/index” view:

<h1>Home</h1>

Routes Configuration

module.exports.routes = {
  '/': {
    controller: 'home',
    action: 'index'
  },
  'get /signup': {
    view: 'signup'
  },
  'post /signup': 'authController.signup',

  'get /login': {
    view: 'login'
  },
  'post /login': 'authController.login',

  '/logout': 'authController.logout'
};

Adding the Passport middleware to our pipeline

In order to have Passport included in the http pages processing pipeline we need to add it in the right position. We do this by editing the “/config/http.js” file:

module.exports.http = {
  middleware: {
    passportInit: require('passport').initialize(),
    passportSession: require('passport').session(),

    order: [
      'startRequestTimer',
      'cookieParser',
      'session',
      'passportInit',
      'passportSession',
      'myRequestLogger',
      'bodyParser',
      'handleBodyParserError',
      'compress',
      'methodOverride',
      'poweredBy',
      'router',
      'www',
      'favicon',
      '404',
      '500'
    ]
  },
};

We place the passportInit and passportSession middle-wares after the session element.


Configuring the Passport local authentication strategy

Now that Passport has been included in the pipeline we need to configure its behaviour to fit with the usage we made of it in our authentication controller. We do this by creating a new “/config/passport.js” file:

var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var bcrypt = require('bcrypt');
var mysql = require('mysql');

passport.serializeUser(function (user, done) {
    return done(null, user.id);
});

passport.deserializeUser(function (id, done) {
    var connection = mysql.createConnection(sails.config.connections.mysql)
    connection.query('select * from users where id = ? limit 1', [id], function (err, result) {
        connection.end();
        if (!err) {
            var user = result.length == 1 ? result[0] : null;
            return done(null, user);
        } else {
            return done(err);
        }
    });
});

passport.use(new LocalStrategy({
    usernameField: 'email',
    passwordField: 'password'
}, function (email, password, done) {
    var connection = mysql.createConnection(sails.config.connections.mysql)
    connection.query('select * from users where email = ? limit 1', [email], function (err, result) {
        connection.end();
        if (!err) {
            var user = result.length == 1 ? result[0] : null;
            if (!!user) {
                bcrypt.compare(password, user.password, function (err, res) {
                    if (!err) {
                        if (!!res) {
                            var returnUser = {
                                email: user.email,
                                id: user.id
                            };
                            return done(null, returnUser);
                        } else {
                            return done(null, null, { message: 'Invalid password' });
                        }
                    } else {
                        return done(err);
                    }
                });
            } else {
                return done(null, null, { message: 'Invalid email' });
            }
        } else {
            return done(err);
        }
    });
}));

Here is a description of what we are doing in each method:

  • serializeUser
    Here we map an user object into its serialised key, so we simply return its id
  • deserializeUser
    Here we map an user’s serialised key into its user object. To do that we read from the database using its id as key.
  • use
    Here is where we place out local authentication strategy. In the first parameter we specify what user object’s fields correspond to the user’s username and password. In the second parameter we define a function that, having received the username and password, checks them against our users table and returns whether a matching user is present or not. We use the BCrypt.compare() method to check our password against the hash we stored at signup time. The 3 values we return through the done() method here are the “err”, “user”, and “info” parameters we were receiving from the passport.authenticate() callback in the authentication controller.

Defining the Authentication policy

This is the policy we’ll apply to our home controller, so that it won’t be possible to access our pages without being authenticated. We create a new “/api/policies/isAuthenticated.js” file:

module.exports = function (req, res, next) {
    if (req.isAuthenticated()) {
        return next();
    }
    else {
        return res.redirect('/login');
    }
};

Now we bind the new policy to every route (hence the “*”), apart for those defined in the AuthController, by editing the “/config/policies.js” file:

module.exports.policies = {
  '*': 'isAuthenticated',
  'authController': true
};

Session integration

A simple session management can be added with the “express-mysql-session” module, in the same way as explained in a previous post.

Implementing a DB migration system (Sails JS)

DB migrations are a very useful way to keep track of the changes to the database and to have them automatically applied as soon as the application starts. There are many implementations of this, a famous one is Flyway, but in this example we want to write our own to work with Sails JS.

As first step we write a dbUpdateService service with the logic. The checkAndUpdate method is able to scan a db_scrpts fonder, detect all the scripts in the format “v{major}_{minor}.sql” and run them against the db depending on its version. The DB version is detected from a config table and is updated after having applied every script.

var mysql = require("mysql");
var fs = require("fs");
var co = require("co");

module.exports = {
    checkAndUpdate: function () {
        var connection = mysql.createConnection(sails.config.connections.mysql);
        connection.query("select `value` from config where `key` = 'version'", function (err, result) {
            connection.end();
            if (!err || err.code == "ER_NO_SUCH_TABLE") {
                var currentVersion = !!result && result.length > 0 ? result[0].value : "0";
                sails.log.info("Database version detected as", currentVersion);
                fs.readdir("db_scripts", function (err, files) {
                    if (!err) {
                        var versions = files.map(function (file) {
                            return file.replace("v", "").replace(".sql", "").replace("_", ".")
                        }).filter(function (version) {
                            return compareVersions(version, currentVersion) == 1;
                        }).sort(compareVersions);
                        if (versions.length > 0) {
                            sails.log.info("Database updates detected:", versions);
                            co(function* () {
                                for (var n = 0; n < versions.length; n++) {
                                    var version = versions[n];
                                    try {
                                        yield updateDb(version);
                                        sails.log.info("Database updated to version", version);
                                    }
                                    catch (err) {
                                        sails.log.error(err);
                                    }
                                }
                            });
                        } else {
                            sails.log.info("Database is up to date");
                        }
                    } else {
                        sails.log.error(err);
                    }
                });
            } else {
                sails.log.error(err);
            }
        });
    }
};

function updateDb(version) {
    return new Promise(function (resolve, reject) {
        fs.readFile("db_scripts/pawnsAndTiles_v" + version.replace(".", "_") + ".sql", "utf8", function (err, data) {
            if (!err) {
                var baseConfig = sails.config.connections.mysql;
                var multipleStatementsConfig = Object.assign({}, baseConfig, { multipleStatements: true })
                var connection = mysql.createConnection(multipleStatementsConfig);
                connection.query(data, function (err, result) {
                    connection.end();
                    if (!err) {
                        updateVersion(version)
                            .then(function () {
                                resolve();
                            })
                            .catch(function (err) {
                                reject(err);
                            });
                    } else {
                        reject(err);
                    }
                });
            } else {
                reject(err);
            }
        });
    });
};

function updateVersion(version) {
    return new Promise(function (resolve, reject) {
        var connection = mysql.createConnection(sails.config.connections.mysql);
        connection.query("update config set `value` = ? where `key` = 'version'", [version], function (err, result) {
            connection.end();
            if (!err) {
                resolve();
            } else {
                reject(err);
            }
        });
    });
};

function compareVersions(version1, version2) {
    var tokens1 = version1.split(".").map(function (token) { return parseInt(token) });
    var tokens2 = version2.split(".").map(function (token) { return parseInt(token) });
    for (var n = 0; n < Math.max(tokens1.length, tokens2.length); n++) {
        if (tokens1.length == n) {
            return -1;
        } else if (tokens2.length == n) {
            return 1;
        } else if (tokens1[n] != tokens2[n]) {
            return tokens1[n] < tokens2[n] ? -1 : 1;
        }
    }
    return 0;
};

Now we need to call our service at bootstrap, so that it will check and update the DB as first thing before actually running the application. To do this in Sails JS we can edit the “config\bootstrap.js” file:

var dbUpdateService = require('api/services/infra/dbUpdateService.js');

module.exports.bootstrap = function(cb) {
    dbUpdateService.checkAndUpdate();
    cb();
};

DB Connection and initialisation

Given that we aren’t using any ORM (Sails comes with Waterline) we manage the DB connection directly with mysql. In this case we added the connection configuration to the “config\connections.js” file:

mysql: {
  adapter: 'sails-mysql',
  host: '127.0.0.1',
  port: 3306,
  user: 'root',
  password: 'root',
  database: ’test-node-sails'
}

We also need to initialise the database with our config table. The dbUpdateService is able to detect if the config table is missing or empty and in that case it assumes that the version is 0, so we can do that in our first script, where we will create the table and set our DB version to 1.0. We can add a script “v1_0.sql” with this content:

CREATE TABLE `config` (
  `key` varchar(50) NOT NULL DEFAULT '',
  `value` varchar(255) NOT NULL DEFAULT '',
  PRIMARY KEY (`key`)
);

INSERT INTO `config` (`key`, `value`)
VALUES ('version','1.0');

A good free IDE to work with mySql is SequelPro.

Invariance, Covariance and Contravariance

Polymorphism

We can assign a derived instance to a base reference, this is trivial 🙂

Base ref1 = new Derived();

It also work with generic classes, as long as the type parameter is the same:

GenericBase<Base> ref2 = new GenericDerived<Base>();
GenericBase<Derived> ref3 = new GenericDerived<Derived>();

Invariance

But we have an error if we try to apply the same principle to the type parameter:

GenericBase<Base> ref4 = new GenericBase<Derived>();  // <= This is not allowed!

The reason is that for a type parameter, by default, we don’t have any polymorphic behavior. We can only use the type originally specified. In this case we say that the type parameter is Invariant.

The ability to have a polymorphic behavior on a type parameter is possible throught the concept of Covariance (and Contravariance, which is the opposite). If a type parameter is either Covariant or Contravariant we say it’s Variant. In .Net Variance in only supported for interfaces and delegates.


Covariance

Is the ability to assign a derived type to a base type parameter. Can be specified by adding the “out” keyword to the type parameter definition.

ICovariantInterface <Base> ref5 = new CovariantBaseClass<Derived>();
ICovariantInterface<Base> ref6 = new CovariantDerivedClass<Derived>();

An example of Covariance in .Net is the IEnumerable interface:

IEnumerable<Base> ref7 = new List<Derived>();

A Covariant type parameter can only be used as return type (hence the “out”).


Contravariance

Is the opposite of the Covariance, it allows to assign a base type to a derived type parameter. Can be specified by adding the “in” keyword to the type parameter definition.

IContravariantInterface<Derived> ref8 = new ContravariantBaseClass<Base>();
IContravariantInterface<Derived> ref9 = new ContravariantDerivedClass<Base>();

An example of Contravariance in .Net is the Action delegate:

Action<Base> action = (Base p) => { };
Action<Derived> ref10 = action;

A Contravariant type parameter can only be used as argument type (hence the “in”). Is used for instance when we want to pass an action that works for the base type to a method that accepts an action that works for the derived type parameter.

ApplyActionToDerived(action, new Derived());

Given:

void ApplyActionToDerived(Action<Derived> action, Derived target)
{
    action(target);
}

Here are the definitions of the classes and interfaces used in this examples:

public class Base
{ }

public class Derived : Base
{ }

public class GenericBase<T>
{ }

public class GenericDerived<T> : GenericBase<T>
{ }

public interface ICovariantInterface<out T>
{
    T ValidCovariantMethod();
    bool InvalidCovariantMethod(T argument); // <= This is not allowed!
}

public class CovariantBaseClass<T> : ICovariantInterface<T>
{
    public T ValidCovariantMethod()
    {
        throw new NotImplementedException();
    }
    public bool InvalidCovariantMethod(T argument)
    {
        throw new NotImplementedException();
    }
}

public class CovariantDerivedClass<T> : CovariantBaseClass<T>
{ }

public interface IContravariantInterface<in T>
{
    bool ValidContravariantMethod(T argument);
    T InvalidContravariantMethod(); // <= This is not allowed!
}

public class ContravariantBaseClass<T> : IContravariantInterface<T>
{
    public bool ValidContravariantMethod(T argument)
    {
        throw new NotImplementedException();
    }
    public T InvalidContravariantMethod()
    {
        throw new NotImplementedException();
    }
}

public class ContravariantDerivedClass<T> : ContravariantBaseClass<T>
{ }

Angular JS – Using Interceptors to handle Http errors

angular.module("myApp.directives", []);
angular.module("myApp.services", []);
angular.module("myApp", [
    "myApp.directives",
    "myApp.services"
])
.config(["$httpProvider", function ($httpProvider) {
    $httpProvider.interceptors.push("myInterceptor");
}]);

//------------
// DIRECTIVES
//------------

angular.module("myApp.directives").directive("myDirective", [function () {
    return {
        restrict: "E",
        replace: true,
        template:
            "<div>" +
                "<h2>myDirective</h2>" +
                "<h3 style='color:red'>{{error}}</h3>" +
            "</div>",
        controller: ["$scope", "myService", function ($scope, myService) {
            $scope.error = null;

            myService.callWorkingApi()
                .then(function () {
                    console.log("myDirective: calling working API success");
                })
                .catch(function () {
                    console.log("myDirective: calling working API failed");
                });

            myService.callBrokenApi()
                .then(function () {
                    console.log("myDirective: calling broken API success");
                })
                .catch(function () {
                    console.log("myDirective: calling broken API failed");
                });

            //Here we are catching up the
            //event raised by the interceptor
            $scope.$on("http-error", function (event, args) {
                $scope.error = args.status;
            });
        }]
    };
}]);

//----------
// SERVICES
//----------

angular.module("myApp.services").service("myService",
    ["$http", '$q', function ($http, $q) {
        var _self = this;

        _self.callWorkingApi = function () {
            return $q(function (resolve, reject) {
                $http.get("/json/data.json")
                    .then(function (result) {
                        resolve(result.data);
                    })
                    .catch(function (error) {
                        reject(error);
                    });
            });
        };

        _self.callBrokenApi = function () {
            return $q(function (resolve, reject) {
                $http.get("/json/wrong.json")
                    .then(function (result) {
                        resolve(result.data);
                    })
                    .catch(function (error) {
                        reject(error);
                    });
            });
        };
    }]);

//-----------
// FACTORIES
//-----------

angular.module("myApp").factory("myInterceptor",
    ["$q", "$rootScope", function ($q, $rootScope) {
        return {
            "response": function (response) {
                return response || $q.when(response);
            },
            "responseError": function (response) {
                if (!!response && !!response.config) {

                    //Here we can perform different
                    //actions based on the error code
                    console.log("myInterceptor:", response.config.url,
                        response.status, response.statusText);

                    //Here we are broadcasting an event
                    //for another directive to catch up and
                    //handle (to display a popup for instance)
                    $rootScope.$broadcast("http-error", response);
                }
                return $q.reject(response);
            }
        };
    }]);

And here is the output:

Angular JS – Decorators and Dynamic Templates to handle a multi-language scenario with $locale + Templates Pre-Caching

Let’s imagine a classic multi-language scenario where we want to have the same page in 2 (or many) different languages. What we normally can do is to setup a family of different URIs (“/it/my-page” and “/uk/my-page” for instance) whose output is the same, with the only difference of the title and the internationalization (i18n) script. Based on that we want to handle all the other differencies (in the data or in the HTML) with Angular and we want to do that in the most transparent way.

Here is an example of the output that we want to receive from the server for the IT and the UK page:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>La mia pagina IT</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
    <script src="i18n/angular-locale_it-it.js"></script>
    <script src="MyApp.js"></script>
</head>
<body>
    <div ng-app="myApp">
        <my-directive></my-directive>
    </div>
</body>
</html>

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>My UK Page</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
    <script src="i18n/angular-locale_en-gb.js"></script>
    <script src="MyApp.js"></script>
</head>
<body>
    <div ng-app="myApp">
        <my-directive></my-directive>
    </div>
</body>
</html>

As we can see the only differences are in the title and the i18n script that we are injecting. The i18n scripts contain many information about the specific languages such as the days’ and months’ names, the currency, the date format and so on. They can of course include copies as well. The content of these scripts can be accessed via the $locale service. We can get these files by installing the whole i18n module (via bower or npm) or, if we are interested in just few of them, we can download them from the Github repository here: https://github.com/angular/bower-angular-i18n.

Here is our app’s code:

angular.module("myApp.directives", []);
angular.module("myApp", [
    "myApp.directives",
    "ngLocale"
]);

angular.module("myApp.directives").directive("myDirective", ["$locale", function ($locale) {
    return {
        restrict: "E",
        replace: true,
        template:
            "<div>" +
                "<h2>myDirective</h2>" +
                "<h3>{{labels.title}}</h3>" +
            "</div>",
        controller: ["$scope", function ($scope) {
            $scope.labels = $locale.labels;
        }]
    };
}]);

$locale.labels is a custom object that we created inside the i18n script for our copies.

Here is the output:

As expected we have the right translations.


Adding Dynamic Templates

To be able to use translated copies is a good beginning, and the i18n scripts can be used for any kind of string, not just copies, that could be different from country to country, including URIs and any other kind of configuration.

But this is often not enough. For instance some country could require a different HTML. Here is a situation where the Dynamic Templates are useful. In this example we want to use them to display the countries’ flags:

angular.module("myApp.directives").directive("myDirective", ["$locale", function ($locale) {
    return {
        restrict: "E",
        replace: true,
        template: function () {
            if ($locale.localeID == "en_GB") {
 
                //UK-specific
                return "<div>" +
                    "<h2>myDirective</h2>" +
                    "<img src='images/Flag_UK.png' width='200'></img>" +
                    "<h3>{{labels.title}}</h3>" +
                "</div>";
            } else {
 
                //IT-specific (default)
                return "<div>" +
                    "<h2>myDirective</h2>" +
                    "<img src='images/Flag_IT.png' width='200'></img>" +
                    "<h3>{{labels.title}}</h3>" +
                "</div>";
            }
        },
        controller: ["$scope", function ($scope) {
            $scope.labels = $locale.labels;
        }]
    };
}]);

Here is the output:

As expected the template is dynamically selected depending on the i18n localeID.


Adding Decorated Services

Another thing that is often different from country to country is data and logic. Since in a well designed application data is coming from services and logic is implemented in services (not in directives, that should just contain the view-model’s logic) it would be useful if we could get a country-specific instance (if exists) of the services we inject.

In this example we want to use a service to get the countries’ capital name. We’ll use Decorators to return the right instance depending on the i18n localeID. In this way the complexity coming from the multi-language scenario will be transparent for our directive’s logic.

angular.module("myApp.directives", []);
angular.module("myApp.services", []);
angular.module("myApp", [
    "myApp.directives",
    "myApp.services",
    "ngLocale"
])
.config(["$provide", function ($provide) {
 
    //If is there a locale-specific version it will
    //get that, otherwise it will get the default one
    $provide.decorator("capitalService", ["$delegate", "$injector", "$locale",
        function ($delegate, $injector, $locale) {
            if ($injector.has("capitalService_" + $locale.localeID)) {
                return $injector.get("capitalService_" + $locale.localeID);
            }
            return $delegate;
        }
    ]);
}]);


//----------
// SERVICES
//----------
 
//UK-specific
angular.module("myApp.services").service("capitalService_en_GB", [function () {
    var _self = this;
 
    _self.getCapital = function () {
        return "London";
    };
}]);
 
//IT-specific (default)
angular.module("myApp.services").service("capitalService", [function () {
    var _self = this;
 
    _self.getCapital = function () {
        return "Roma";
    };
}]);


//------------
// DIRECTIVES
//------------
 
angular.module("myApp.directives").directive("myDirective", ["$locale", function ($locale) {
    return {
        restrict: "E",
        replace: true,
        template: function () {
            if ($locale.localeID == "en_GB") {
 
                //UK-specific
                return "<div>" +
                    "<h2>myDirective</h2>" +
                    "<img src='images/Flag_UK.png' width='200'></img>" +
                    "<h3>{{labels.title}}</h3>" +
                    "<p>{{labels.capitalIs}} {{capital}}</p>" +
                "</div>";
            } else {
 
                //IT-specific (default)
                return "<div>" +
                    "<h2>myDirective</h2>" +
                    "<img src='images/Flag_IT.png' width='200'></img>" +
                    "<h3>{{labels.title}}</h3>" +
                    "<p>{{labels.capitalIs}} {{capital}}</p>" +
                "</div>";
            }
        },
        controller: ["$scope", "capitalService", function ($scope, capitalService) {
            $scope.labels = $locale.labels;
            $scope.capital = capitalService.getCapital();
        }]
    };
}]);

Here is the output:

As expected, depending on the i18n localeID, i’m getting the right instance of capitalService.


Scanning for Decorators

Decorators are great, but they have to be setup one by one. This is going to produce tons of parboiled code in an enterprise project, definitely not what we want to have to deal with. To solve this problem we would like to be able to auto-generate all of this configuration by scanning for the components and detecting which of them needs a decoration. In order for this to work we’ll follow the convention that we’ll append the i18n localeID to the components that are specific for a language.

The following algorithm works with both services and directives. In this example we’ll use it to decorate the capitalService as before and to replace the Dynamic Template with a decorated directive (decorating a directive should be avoided, only the template should dynamically change. If a directive’s logic needs to change from country to country the variable part should be moved out into a decorated service).

The algorithm has been implemented as an Angular provider, here is the code:

angular.module("myApp.directives", []);
angular.module("myApp.services", []);
angular.module("myApp", [
    "myApp.directives",
    "myApp.services",
    "ngLocale"
])
 
//For providers we must add "Provider" at the end!
.config(["decoratorsProvider", function (decoratorsProvider) {
     decoratorsProvider.bootstrapDecorators(["myApp.directives", "myApp.services"], ["en_GB"]);
}]);
 
 
//------------
// DIRECTIVES
//------------
 
//UK-specific
angular.module("myApp.directives").directive("myDirective_en_GB", ["$locale", function ($locale) {
    return {
        restrict: "E",
        replace: true,
        template:
            "<div>" +
                "<h2>myDirective_en_GB</h2>" +
                "<img src='images/Flag_UK.png' width='200'></img>" +
                "<h3>{{labels.title}}</h3>" +
                "<p>{{labels.capitalIs}} {{capital}}</p>" +
            "</div>",
        controller: ["$scope", "capitalService", function ($scope, capitalService) {
            $scope.labels = $locale.labels;
            $scope.capital = capitalService.getCapital();
        }]
    };
}]);
 
//IT-specific (default)
angular.module("myApp.directives").directive("myDirective", ["$locale", function ($locale) {
    return {
        restrict: "E",
        replace: true,
        template:
            "<div>" +
                "<h2>myDirective</h2>" +
                "<img src='images/Flag_IT.png' width='200'></img>" +
                "<h3>{{labels.title}}</h3>" +
                "<p>{{labels.capitalIs}} {{capital}}</p>" +
            "</div>",
        controller: ["$scope", "capitalService", function ($scope, capitalService) {
            $scope.labels = $locale.labels;
            $scope.capital = capitalService.getCapital();
        }]
    };
}]);
 
 
//----------
// SERVICES
//----------
 
//UK-specific
angular.module("myApp.services").service("capitalService_en_GB", [function () {
    var _self = this;
 
    _self.getCapital = function () {
        return "London";
    };
}]);
 
//IT-specific (default)
angular.module("myApp.services").service("capitalService", [function () {
    var _self = this;
 
    _self.getCapital = function () {
        return "Roma";
    };
}]);
 
 
//-----------
// PROVIDERS
//-----------
 
angular.module("myApp").provider("decorators", ["$provide", function ($provide) {
    var _self = this;
 
    _self.bootstrapDecorators = function (modulesNames, localeIDs) {
        scanDecorators().forEach(createDecorator);
 
        function scanDecorators() {
            var decorators = [];
            modulesNames.forEach(function (moduleName) {
                var module = angular.module(moduleName);
                module._invokeQueue.forEach(function (element) {
                    var elementType = element[1];
                    var elementName = element[2][0];
 
                    localeIDs.forEach(function (localeID) {
                        if (elementName.endsWith("_" + localeID)) {
                            var originalName = elementName.replace("_" + localeID, "");
                            if (!decorators.some(function (decorator) {
                                return decorator.originalName == originalName;
                            })) {
                                decorators.push({
                                    originalName: originalName,
                                    elementType: elementType
                                });
                            }
                        }
                    });
                });
            });
            return decorators;
        };
 
        function createDecorator(decorator) {
 
            //For directives we must add "Directive" at the end!
            var originalName = decorator.elementType == "directive"
                ? decorator.originalName + "Directive"
                : decorator.originalName;
 
            $provide.decorator(originalName, ["$delegate", "$injector", "$locale",
                function ($delegate, $injector, $locale) {
                    var decoratedName = decorator.originalName + "_" + $locale.localeID;
 
                    //For directives we must add "Directive" at the end!
                    decoratedName = decorator.elementType == "directive"
                        ? decoratedName + "Directive"
                        : decoratedName;
 
                    if ($injector.has(decoratedName)) {
                        return $injector.get(decoratedName);
                    }
                    return $delegate;
                }
            ]);
        }
    };
 
    _self.$get = angular.noop;
}]);

Here is the output:


Detecting Dynamic Templates

Now that we are able to automatically setup decorators based on a naming convention it would be cool to be able to do the same with Dynamic Templates. In this example we’ll use a service to automatically get the localized version of a template based on the current localeID. In order for this to work we need to have all the templates available into the Angular template cache, so we’ll be able to scan for them. Downloading all the templates in advance is anyway a good practice, since it gives a more reactive user experience.

To simplify the example we removed the services and the decorators, so we’ll be able to better focus on the new concept. Here is the code:

angular.module("myApp.templates", []);
angular.module("myApp.directives", []);
angular.module("myApp.services", []);
angular.module("myApp", [
    "myApp.templates",
    "myApp.directives",
    "myApp.services",
    "ngLocale"
]);
 
 
//-----------
// TEMPLATES
//-----------
 
//We pre-load all the templates into the template cache. It's possible
//to auto-generate this from the real .html files, for instance via Grunt.
angular.module("myApp.templates").run(["$templateCache", function ($templateCache) {
 
    //UK-specific
    $templateCache.put("/myApp/templates/myDirective_en_GB.html",
        "<div>" +
            "<h2>myDirective</h2>" +
            "<img src='images/Flag_UK.png' width='200'></img>" +
            "<h3>{{labels.title}}</h3>" +
        "</div>");
 
    //IT-specific (default)
    $templateCache.put("/myApp/templates/myDirective.html",
        "<div>" +
            "<h2>myDirective</h2>" +
            "<img src='images/Flag_IT.png' width='200'></img>" +
            "<h3>{{labels.title}}</h3>" +
        "</div>");
}]);
 
 
//------------
// DIRECTIVES
//------------
 
angular.module("myApp.directives").directive("myDirective",
    ["$locale", "templatesManager", function ($locale, templatesManager) {
        return {
            restrict: "E",
            replace: true,
            templateUrl: templatesManager.getLocalizedTemplate("/myApp/templates/myDirective.html"),
            controller: ["$scope", function ($scope) {
                $scope.labels = $locale.labels;
            }]
        };
    }]);
 
 
//----------
// SERVICES
//----------
 
angular.module("myApp.services").service("templatesManager",
    ["$templateCache", "$locale", function ($templateCache, $locale) {
        var _self = this;
 
        _self.getLocalizedTemplate = function (templateName) {
            var localizedName = templateName.replace(".html", "_" + $locale.localeID + ".html");
 
            if (!!$templateCache.get(localizedName)) {
                return localizedName;
            } else {
                return templateName;
            }
        };
    }]);

Angular JS – Transclude and Require

Transclude allows us to define a directive’s content, or part of it, directly from the content we put inside it. This is a very flexible way to compose an interface by directly putting directives one inside the other. The place in the template where to place the inner content can be defined with the ng-transclude directive.

However the Transclude’s default behavior doesn’t always work as expected, so in this example we’ll add some magic in order to make it work in the right way.

<div ng-app="myApp">
    <my-container current-color="red">
        <!--I can have bound HTML-->
        <p>{{description}}</p>
 
        <!--I can have bound directives-->
        <my-content color="red" current-color="currentColor" next-color="blue"></my-content>
        <my-content color="blue" current-color="currentColor" next-color="green"></my-content>
        <my-content color="green" current-color="currentColor" next-color=""></my-content>
    </my-container>
</div>

angular.module("myApp.directives", []);
angular.module("myApp", [
    "myApp.directives"
]);
 
angular.module("myApp.directives").directive("myContainer", function () {
    return {
        restrict: "E",
        replace: true,
        transclude: true,
        scope: {
            currentColor: "@"
        },
        template:
            "<div>" +
                "<h3>{{title}}</h3>" +
                "<div id='transclude'></div>" +
                "<h3>{{footer}}</h3>" +
            "</div>",
        controller: ["$scope", function ($scope) {
            $scope.title = "This is the title";
            $scope.description = "Click on the colors!";
            $scope.footer = "This is the footer";
        }],
        link: function ($scope, elem, attrs, ctrl, transclude) {
            /*
            http://angular-tips.com/blog/2014/03/transclusion-and-scopes
            By default, the transcluded content is not bound to the directive's
            isolate scope, as one could think, but is instead bound to the outer
            controller's one.
 
            ---------------------------        -------------
            | Outer controller        | =====> | Directive |
            |                         |        -------------
            | (the main in this case) |        -----------------------
            |                         | -----> | Transcluded content |
            ---------------------------        -----------------------
 
            =====> = Isolated scope
            -----> = Non-isolated scope
 
            Because of this both $scope.title and $scope.footer would be rendered,
            since they are included in the template, while $scope.description
            wouldn't, because is referred in the transcluded content.
 
            The transclude() function allows us to change this behavior by setting
            what scope the transcluded content should to be compiled with. After
            the content has been compiled we can manually insert it where we want.
 
            IMPORTANT
            Since we override the default behavior in this way we don't have to use
            the ng-transclude directive to specify where the transcluded content
            should be placed. Since that directive automatically inject the
            content as it's generated in the default way, if we do so we'll end
            up having it rendered twice.
            */
            transclude($scope, function (clone) {
                elem.find("#transclude").append(clone);
            });
        }
    };
});
 
angular.module("myApp.directives").directive("myContent", function () {
    return {
        restrict: "E",
        replace: true,
        require: "^myContainer", // <-- Must be insert within a myContainer controller
        scope: {
            color: "@",
            currentColor: "=",
            nextColor: "@"
        },
        template:
            "<div style='background-color:{{color}}'>" +
                "<h3>{{title}}</h3>" +
                "<div ng-if='currentColor == color'>" +
                    "<button ng-click='onNext()'>Next</button>" +
                "</div>" +
            "</div>",
        controller: ["$scope", function ($scope) {
            $scope.title = "This is the content " + $scope.color;
            $scope.onNext = function () {
                $scope.currentColor = $scope.nextColor;
 
                //We can access to the parent's controller
                $scope.$parent.title = $scope.color;
            };
        }]
    };
});


Referring the $parent from the view

As we saw before we can user $scope.$parent to refer the parent’s scope (this is more safe if we use the “require” option to enforce our component to be used with the right parent). However there is some consideration to do when accessing the $parent from the view. The $parent keyword is managed by angular and isn’t always referring the scope we suppose. This is because several angular directives, such as ng-if and ng-repeat create their own child scope, so if we refer to the $parent from inside one of them we’ll just get their outer scope and not the scope of our component’s parent. And easy way to be sure about the parent we are referring to is to map a reference in our scope:

$scope.parent = $scope.$parent;

Angular JS – HttpService to cache $http calls between different apps

angular.module("common.services")
    .service("httpCachingService", ["$http", '$q', "$window",
        function ($http, $q, $window) {
            var _self = this;

            var _ns = $window.httpCachingService = $window.httpCachingService || {
                    items: []
                };

            _self.get = function (uri) {
                return $q(function (resolve, reject) {
                    var filteredItems = _ns.items.filter(function (item) {
                        return item.uri == uri;
                    });

                    var item = filteredItems.length > 0 ? filteredItems[0] : null;

                    if (!!item) {
                        if (item.ready) {
                            resolve(item);
                        } else {
                            item.listeners.push(function () {
                                resolve(item);
                            });
                        }
                    } else {
                        item = {
                            uri: uri,
                            ready: false,
                            data: null,
                            listeners: []
                        };

                        _ns.items.push(item);

                        $http.get(uri)
                            .then(function (result) {
                                item.data = result.data;
                                item.ready = true;

                                item.listeners.forEach(function (listener) {
                                    listener();
                                });

                                item.listeners = [];
                                resolve(item);
                            })
                            .catch(function (error) {
                                reject(error);
                            });
                    }
                });
            };

            _self.invalidate = function (uri) {
                _ns.items = _ns.items.filter(function (item) {
                    return (!!uri && item.uri != uri) || !item.ready;
                });
            };
        }]);