Showing posts with label angularjs. Show all posts
Showing posts with label angularjs. Show all posts

Tuesday, 13 May 2014

angular-fullstack yeoman generator - use less version of bootstrap

I use the angular-fullstack yeoman generator, but it installs the compiled bootstrap css and I want to use the less version.

Here is how I hack the yeomen generated source to change it to use the less version of bootstrap:

# install less and less grunt task:
sudo npm install -g less
npm install --save-dev grunt-contrib-less

# create app/styles/main.less file:
@import "../bower_components/bootstrap/less/bootstrap.less";
@import "inputs.less";
@import "../bower_components/bootstrap/less/utilities.less";

# rename the old app/styles/main.css to app/styles/inputs.less:
mv app/styles/main.css app/styles/inputs.less

# create less grunt tasks in Gruntfile.js:
    // build our css
    less: {
      dev: {
        files: {
          "<%= yeoman.app %>/styles/main.css": "<%= yeoman.app %>/styles/main.less"
        }
      },
      dist: {
        options: {
          cleancss: true
        },
        files: {
          "<%= yeoman.dist %>/styles/main.css": "<%= yeoman.app %>/styles/main.less"
        }
      }
    },

# add 'less' file extension to files in the styles section of the watch grunt task:
files: ['<%= yeoman.app %>/styles/{,*/}*.{css,less}'],

# add 'less:dev' to tasks in the styles section of the watch grunt task:
tasks: ['less:dev', 'newer:copy:styles', 'autoprefixer']

# also, add 'less:dev' to the debug and serve grunt tasks, and 'less:dist' to the build grunt task

# add exclude entry to the bower-install grunt task to prevent it from being injected into index.html:
exclude: [ 'bower_components/bootstrap/dist/css/bootstrap.css' ]

# add generated css to .gitignore:
echo app/styles/main.css >> .gitignore

Monday, 31 March 2014

MEAN stack build system

MEAN stack build system

The MEAN stack is [M]ongo, [E]xpress, [A]ngular, [N]ode.
Back end:
The server is built using Express, which is built on top of Node.
The database used by the server is MongoDB.
Front end:
The client is built using Angular.
The less version of Bootstrap is used for CSS.
I use Yeoman and the angular-fullstack generator to generate my MEAN stack scaffold.
yo angular-fullstack [app]
Development tools:
npm is used for the server dependencies; these are listed in package.json
Bower is used for the client dependencies; these are listed in bower.json
Grunt is used for the build system. The configuration is stored in Gruntfile.js
The file system structure is as follows:
site/
  app/            # client source code 
  lib/            # server source code
  test/           # tests
  public/         # client production build destination
  Gruntfile.js    # Grunt configuration
  package.json    # server dependencies
  bower.json      # client dependencies
  server.js       # Express server entry-point
Grunt plugins:
grunt-bower-install:
When new dependencies are added with bower, grunt-bower-install will automatically inject their css and js into index.html.
In the grunt.initConfig block of Gruntfile.js:
'bower-install': {
    app: {
        html: '<%= yeoman.app %>/index.html',
        ignorePath: '<%= yeoman.app %>/'
    }
},
grunt-file-blocks:
When new front end components added in the source tree, grunt-file-blocks will automatically inject the javascript into index.html and the less into main.less.
In the grunt.initConfig block of Gruntfile.js:
fileblocks: {
    options: {
        removeFiles: true,
        templates: {
            less: '@import \'${file}\';'
        }
    },
    js: {
        src: 'app/index.html',
        blocks: {
            components: { cwd: 'app', src: 'components/**/*.js' }
        }
    },
    less: {
        src: 'app/css/main.less',
        blocks: {
            components: { cwd: 'app', src: 'components/**/*.less' }
        }
    }
},
Development workflow:
File are monitored for changes and the front or back end are reloaded as required.
nodemon and LiveReload are run concurrently:
In the grunt.initConfig block of Gruntfile.js:
concurrent: {
    dev: {
        tasks: ['nodemon', 'watch'],
        options: {
            logConcurrentOutput: true
        }
    }
},
Back end changes are monitored by nodemon. If a file required by the server is changed, node will be relaunched, restarting the server.
In the grunt.initConfig block of Gruntfile.js:
nodemon: {
    dev: {
        options: {
            file: 'server.js',
            ignoredFiles: ['app', 'test', 'node_modules'],
            watchedExtensions: ['js']
        }
    }
},
Front end changes are monitored by LiveReload. If a file required by the client is changed, assets will be rebuild (eg less converted to css) and the browser will be refreshed
In the grunt.initConfig block of Gruntfile.js:
watch: {
    app: {
        options: { livereload: true },
        files: [
            'app/index.*',
            'app/css/*',
            'app/components/**/*.js',
            'app/images/*'
        ]
    },
    less: {
        tasks: ['less'],
        files: ['app/css/main.less', 'app/components/**/*.less']
    }
},
less: {
    build: {
        files: { 'app/css/main.css': 'app/css/main.less' }
    }
}
This article is inspired by Shawn Dahlen’s post on the build system 4dashes uses.

Tuesday, 28 January 2014

Angular configuration - default http headers

Help prevent CSRF attacks by setting the X-Requested-By / X-Posted-By header

angular.module('myApp')
    .config(function($httpProvider) {
        $httpProvider.defaults.headers.common['X-Requested-By'] = 'myApp';
    });

angular.module('myApp')
    .config(function($httpProvider) {
        $httpProvider.defaults.headers.post['X-Posted-By'] = 'myApp';
    });

angular.module('myApp')
    .config(function($httpProvider) {
        $httpProvider.defaults.headers.put['X-Posted-By'] = 'myApp';
    });

Change the default headers at runtime:

$http.defaults.common['X-Auth'] = "foobar";

Security.StackExchange discussion

Monday, 27 January 2014

Angular directives - require

If we want to expose an API to other directives, we should require a controller on the directive.

We can bind a controller to the directive with require. Multiple controllers can be required by using an array of strings.

app.directive('foo', function() {
  return {
    require: 'bar',
  }
});

Link function

When a controller is required, it will be passed as the 4th argument to the link function:

app.directive('foo', function() {
  return {
    require: 'bar',
    link: function (scope, element, attrs, bar) {
    }
  }
});

When multiple controllers are required, they will be passed as an array to the link function:

app.directive('foo', function() {
  return {
    require: ['bar, baz'],
    link: function (scope, element, attrs, controllers) {
      var bar = controllers[0];
      var baz = controllers[1];
    }
  }
});

Controller location

We can control where the controller can be found by prefixing the controller name as follows:

no prefix

The required controller must be specified on the directive itself. An exception is thrown if no controller is found.

?

Make the controller optional - if it is not found in the directive provided, null is passed as the 4th argument to
the link function.

ˆ

Walk up the parent chain until the controller is found



Combine the previous two options - walk up the parent chain until the controller is found, and pass null if none is found


Wednesday, 22 January 2014

Angular directives - isolate scope

If you don't want a directive to use its parent's scope, create isolate scope:

app.directive("widget", function() {
    return {
        scope : {},    // creates isolate scope
        ...
}

When using isolate scope, obtain access to the parent scope by binding some members:

app.directive("widget", function() {
    return {
        scope : {
            foo: '@' // binds to parent's $scope.foo
        },
        ...
}

3 bindings to parent scope exist:

    '@': 1-way, by value, as a string (updates in the directive are not propagated to the parent scope)
    '=': 2-way, by reference (updates in the directive will update the parent scope)
    '&': expression (the expression is executed in the context of the parent scope)

1-way binding:

app.directive("widget", function() {
    return {
        restrict: 'E', // can only be used as an element
        scope: {
            foo: '@'   // imports $scope.foo by value
        },
        link: function(scope, element, attrs) {
            // do something with attrs.foo
        }


<widget foo="blah"></widget>

In the directive's link function, attrs.foo === "blah".

Model expressions can be parsed too:

<widget foo="{{ model }}"></widget>

In the directive's link function, attrs.foo is a string containing the value in $scope.model.

2-way binding:

app.directive("widget", function() {
    return {
        restrict: 'E', // can only be used as an element
        scope: {
            foo: '='   // imports $scope.foo by reference
        },
        link: function(scope, element, attrs) {
            // do something with attrs.foo
        }


<widget foo="bar"></widget>

In the directive's link function, attrs.foo === $scope.bar. Updating attrs.foo will result in $scope.bar being updated too.

expression binding:

app.directive("widget", function() {
    return {
        restrict: 'E', // can only be used as an element
        scope: {
            foo: '&'   // foo() executed as expression on $scope
        },
        link: function(scope, element, attrs) {
            // attrs.foo() will execute the expression
        }


<widget foo="bar()"></widget>

In the directive's link function, attrs.foo() will execute $scope.bar()

pass parameters to expression:

Special syntax is required to pass parameters to the expression.

In the directive, foo({p1: val}) will pass val to expression foo, which is function bar(p1) on $scope