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

Saturday, May 2, 2015

Mobile App with Ionic, ES6, AngularJS 1.4

While the world is awaiting for Ionic 2 based on Angular 2, I'm going to show you how to use little bit less bleeding edge technologies, but existing and working already today and still much more exciting than old ES5. Really, when you try arrow functions, you won't use ES5 again.

I will use both generator-gulp-angular and ionic-cli to generate project. First one for ES6 and tests running, second one for js-to-mobile builds.

Step 1:  Install generator-gulp-angular


npm install -g yo gulp bower

npm install -g generator-gulp-angular

Step 2: Install Ionic:


npm install -g cordova

npm install -g ionic


Step 3: Generate ionic files

We will start with Ionic, because Ionic will create a folder and will remember name of this folder somewhere inside:

ionic start example

Where example is the name of our project.
We actually don't need whole content of generated folder, but we have to do previous step to save name of folder inside ionic.
Now we will rename it:

mv example example_ionic

Step 4: Generate project folder

Let's create a folder again and this time it will be fulfilled by generator-gulp-angular:

mkdir example
cd example
yo gulp-angular example

On generation steps please select:
1) AngularJS 1.3 (or later version if in your time you have later version)
2) ng-modules: animate, touch, sanitize
3) jQuery: none
4) rest: ng-resource
5) router: ui-router
6) UI-framework: none (we will use Ionic for it)
7) CSS preprocessor - whatever you prefer
8) JS preprocessor: ES6 Babel
9) html template engine - whatever you prefer

Now we have generated project and it's the structure we will use as main.

Step 5: Merge

Let's copy ionic-generated files to our project:

cp ../example_ionic/config.xml ./
cp ../example_ionic/ionic.project ./
cp -R ../example_ionic/plugins ./plugins
cp -R ../example_ionic/hooks ./hooks

From ../example_ionic/package.json copy branch "cordovaPlugins" into your ./package.json.

Step 6: Change AngularJS version to 1.4

If in your time generator-gulp-angular has AngularJS version 1.4 or higher - skip this step.

Otherwise, let's change version of AngularJS and modules to 1.4.
To do this, you need a text editor (or your favorite IDE). Good chance to try new shiny Visual Studio Code :)

In bower.json, replace all entries of AngularJS 1.3 modules dependencies to 1.4.x:
Hint: VS Code replace shortcut in OS X is alt+cmd+F.

Replace all entries, save your edits and:

bower install

Step 7: Add latest ionic as bower dependency

bower install ionic#~1.x --save

On versions conflicts, select most latest versions and prefix your answers with "!".

Step 8: Add platforms

mkdir www

ionic platform add ios

ionic platform add android

Step 9: Build

In gulpfile.js, change in the options: 
dist: 'dist' 
to 
dist: 'www'

Now try to build your ES6 code:

gulp

You will run this command before each app deployment.

Check if ionic-cli works:

ionic serve

You should see something like this:

It doesn't look like mobile app, so let's use power of Ionic framework to change it!
Please interrupt ionic-cli by Ctrl+C for now.

Step 10: Ionize

In src/index.html, add link to ionic CSS into block "build:css": 

<!-- endbower -->
      here
<!-- endbuild -->

so it becomes:

    <!-- build:css({.tmp/serve,src}) styles/vendor.css -->
    <!-- bower:css -->
    <!-- run `gulp inject` to automatically populate bower styles dependencies -->
    <!-- endbower -->
    <link href="../bower_components/ionic/release/css/ionic.css" rel="stylesheet">
    <!-- endbuild -->

Now add links to JS files into first build:js block, after endbower and before endbuild line, so it becomes:

    <!-- build:js(src) scripts/vendor.js -->
    <!-- bower:js -->
    <!-- run `gulp inject` to automatically populate bower script dependencies -->
    <!-- endbower -->
    <script src="../bower_components/ionic/release/js/ionic.js"></script>
    <script src="../bower_components/ionic/release/js/ionic-angular.js"></script>
    <!-- endbuild -->

You need only these 2 files, everything else will be added by Gulp tasks.

Now replace 

<div ui-view></div>

with

<ion-nav-view></ion-nav-view>

And remove Google Analytics template code.

In src/app/index.js add Ionic as dependency, so list should look like this at this moment:
angular.module('example', ['ionic', 'ngAnimate', 'ngTouch', 'ngSanitize', 'ngResource', 'ui.router'])

To make UI of our app little bit more mobile friendly, let's take content of file ../example_ionic/www/templates/tab-dash.html, generated by ionic and put it into our src/app/main/main.html (replacing all lines).

Let's see what we have in result:

gulp serve

Project has been ionized!

While gulp serve work, every change we make in html/css/js source files will be reflected in the browser, so we don't need to run this task after each change manually - very handy for development.

Step 11: Use the power of ES6

To help you love ES6, I will show you some tricks, how to use it without any pain with AngularJS 1.4.

ControllerAs

ControllerAs it's a very important thing, it's not just handy feature, it makes your code much more clean and especially beautiful with ES6.
So, as a first step, in src/app/index.js replace 
  controller: 'MainCtrl'
with
  controller: 'MainCtrl as Main'
Now in html template you can use Main as reference to your controller and inside controller you can bind properties to "this" instead of $scope. More details about usage of controllerAs you can find in an article of very smart guys from thoughtram.

Services and Controllers

Services and Controllers can be easily presented as ES6 classes. To not duplicate list of dependencies in $inject, you can add string 
'ngInject';
inside function with list of dependencies. In case of class it's constructor.
Example:

class ExampleController {
  constructor($timeout) {

    'ngInject';
    this.$timeout = $timeout;
  }
}


export default ExampleController;

Directives, Filters and Resources

But some things are much more simple when we use them as old good functions.
Examples:

Resource:

function SomeLog($resource, Config, Token) { 

  'ngInject';
  return $resource(Config.apiUrl + '/events/:token', {token: Token.get()}, {
    count: {
      method: 'GET',
    
      url: Config.apiUrl + '/events/count/:token',     
      isArray: false,     
      transformResponse: function (data) {
        data = JSON.parse(data);
        return {'count': data};
      }
    }
  });
}

export default SomeLog;


Directive:

function exmpNavigation($state) { 
  'ngInject';
  return {
    restrict: '',
    templateUrl: 'components/exmpNavigation/template.html',
    controllerAs: 'cNav',
    controller: function () {
      this.go = function (route) {
        $state.go(route); 
      };
    }
  };
}
export default exmpNavigation;


Step 12: Mobile App

When your app is interesting enough to be tested on mobile device, create a build.
Don't forget to compile from sources:
gulp

And then we can create the build for iOS:
ionic build ios

and even test it, if you use OS X:
ionic emulate ios

More info about builds, including Android, can be found in Ionic tutorial.

One more interesting feature Ionic has: Ionic View. It's amazing thing and I recommend you to try it - pretty simple to use and saves tones of time for debug and testing on real devices. To start, just run:
ionic upload

All the power is in your hands (and brain)

Now you can use all the power of generator-gulp-angular, including handy shortcuts for testing (gulp test, gulp protractor), you can write awesome code in ES6 to build best in the universe mobile apps with Ionic, powered by the best AngularJS version yet. 
Good luck! :)

Wednesday, December 31, 2014

Testing AngularJS app with Protractor in multiple browsers (OS X)

Different browsers have different quirks, so let's try to run e2e tests in multiple major browsers.
We can't run IE in OS X, Opera is customized Chrome, so we need: Chrome, Safari,  Firefox.


Protractor has option to run specs in multiple browsers, called multiCapabilities.
By default all browsers run tests simultaneously, but in my experience it leads to errors ("failed expectations"), because sometimes tests trying to fulfill same records and it leads to conflicts.
To run tests in browsers consecutive, we can use "maxSessions: 1" in Protractor config.
So result is:

multiCapabilities: [
{
'browserName': 'chrome'
}
, {
'browserName': 'safari'
}
, {
'browserName': 'firefox'
}
],
maxSessions: 1,


If you use browserSync to run app locally, add "ghostMode: false" in browserSync config to avoid conflicts in launched versions of app when testing.

But software is often more complicated than we expect.
First: we can't run actual version of Firefox on OS X, even with new Selenium drivers.
Bug has status "Fixed" but nobody cares about regression. Update: to run tests in Firefox, use Protractor 1.8+, it will install Selenium 2.45, where bug is fixed.

So now we have Chrome and Safari in list.
Safari don't want to run too, hehe :) But it's fixable at least, thanks to this detailed answer.
Get actual version of Selenium driver jar here: http://central.maven.org/maven2/org/seleniumhq/selenium/selenium-safari-driver/
rename extension to .zip, 
unzip file 
and then in folder 'org/openqa/selenium' find file 'SafariDriver.safariextz' (it's not random letters :)) and double-click it.

After that, we can run tests in Safari, hurray!
And it's not the end of this story :)
Selenium can't navigate in browsing history of Safari (known issue), so if you use 


browser.navigate().back();

replace it to:

// before changing URL
var prevUrl = browser.getCurrentUrl();
// here some changing URL action, maybe click()
// ...
// and then
browser.navigate().to(prevUrl);

It's pretty obvious fix.
And now we can run Protractor tests in Chrome and Safari consecutive. It took few hours of my time to find all these fixes and workarounds, so I hope this article will help somebody to save time :)



Wednesday, November 12, 2014

AngularJS 1.3 and ES6 - how to use it already today

ES6, AtScript

Next version of Angular will use AtScript - superset of ES6 (upcoming version of JavaScript). So, if you write in ES5 (current version of JavaScript), you already write in AtScript, just without all of these optional additions - annotations, types and introspections. Yes, they will stay optional, but it's nice to have them.

We can't use AtScript in existing projects yet (and Angular 2 will be ready even later than AtScript), but we already can use ES6 with AngularJS 1.3, best Angular yet (current stable branch), with all cool things ES6 going to give us. Take it now! :)

How to

Update Feb 18, 2015:

Time goes and in few weeks after publishing this article, angular-gulp generator have implemented support of ES6. I highly recommend to use it instead of generator-angular, and all ES6 support will be provided "out of the box": https://github.com/Swiip/generator-gulp-angular


I'll show you how to do it on example of the project, bootstrapped by Yeoman's generator-angular. Maybe not top-notch folders structure, but easy to start with.
Source code of project:

Step 1 - install modules

When we have base of project, let's add components:
Grunt will transpile ES6 to ES5 (I know I know, Gulp is our new trend, but.. next time)

npm install grunt-traceur --save-dev

PhantomJS doesn't support ES5 even, so we will run unit tests in Chrome:

npm install karma-chrome-launcher --save-dev

We need traceur runtime in app:

bower install traceur --save

That's all.

Step 2 - Structure

In structure of this project most convenient place I found for ES6 files is /app/es6 - I just copied content of 'scripts' folder here. Now I can use watch, livereload, build project and run tests. Awesome.

Step 3 - Gruntfile

Example of Gruntfile.js
And what has been changed in file, generated by generator-angular:
We add new task, 'traceur' and add it to build, test, serve tasks. 

Step 4 - Unit tests config

replace PhantomJS by Chrome (it will run without taking focus, in background) and add traceur.min.js link.

Step 5 - index.html

Just add link to traceur.min.js

Enjoy ES6!

Now you can use ES6 in your AngularJS project. For example, I use ES6 classes for Services, because I like it and it works :)
Traceur will compile AtScript in the same way it compile ES6 for us in this project, so it's nice to meet Traceur already now and start use advantages of new technologies.

Additional Links

Wednesday, August 27, 2014

How to not minify already minified on each "grunt build" in yeoman

Long title, yeah.

Let's speed-up Grunt a little

In some of my projects I use yeoman (generator-angular), and on each "grunt build" Grunt runs minification of all JS files from the 'vendor' list (and from 'scripts' also, but it's not a problem).
It takes a long time, especially if you use a lot of external modules.
But most of them already have minified versions, so why to waste a time to minify them again?

I've made Grunt to skip minification of 'vendor' block and it decreased time of 'grunt build' execution in 5 times.

But it wasn't easy (that's why I decided to write this article).

You can't just ask usemin to skip 'vendor', but you can ignore configuration, made by usemin for uglify. To do this, add 'onlyScripts' target to your uglify task:

    uglify:        {
      onlyScripts: {
        files:   [{
          dest: '<%= yeoman.dist %>/scripts/scripts.js',
          src:  ['.tmp/concat/scripts/scripts.js']
        }]
      }
    }

Also, now uglify will not copy your vendor.js from temporary folder, so add "vendorJS" section to "copy" task:

copy:
      //...
      vendorJS: {
        expand: true,
        cwd:    '.tmp/concat/scripts/',
        dest:   '<%= yeoman.dist %>/scripts/',
        src:    'vendor.js'
      }

Then, in "build" task, set target of uglify to 'onlyScripts' and copy vendor.js:

  grunt.registerTask('build', [
    'jshint',
    'clean:dist',
    //'wiredep',
    // ...
    'useminPrepare',
    //'concurrent:dist',
    'autoprefixer',
    'concat',
    'ngAnnotate',
    'copy:dist',
    'cssmin',
    'uglify:onlyScripts',
    'copy:vendorJS',
   // ...
  ]);

About commented out lines:
 "wiredep" - this task will replace all .min.js-links (in "vendors" block) to .js-links. We don't want to allow it (otherwise our vendor.js will be huge non-minified file).
 "concurrent:dist" - when this task is commented out, Grunt works much faster on MBP with 2 cores. I don't know why. Try it, maybe it will help you too.


Sunday, August 25, 2013

Automation of Grunt builds in WebStorm/PHPStorm

It's very handy to use Yeoman and Grunt to build AngularJS (and not only) applications. And I'll write here how make it even more comfortable in JetBrains IDE.

Default installation of Yeoman is very good for development on localhost. But it's not always possible - some databases and services just can't be on localhost, even their mocks. For example: ActiveDirectory - I don't even want to imagine, how many time I'll spend to make small copy of AD on my localhost.

For deployment I use beanstalk - all I need to deploy on development server is press "git commit and push" button in IDE (I use PHPStorm because in my current projects backend is in PHP).
But before that, I have to run "grunt build" to put all results to the "dist" folder.

There is 2 ways to automate grunt builds in PHPStorm/WebStorm:

1) File Watchers - can be used, when repository contains only 1 application (because of "working directory" argument in the File Watcher settings);
2) Rebuild manually by running external tool from context menu or executing "grunt build" in console;

To run grunt build in Windows console:

1) add path to nodejs folder and %APPDATA%\Roaming\npm\ in your system PATH variable
2) open console in IDE (shift+ctrl+x)
3) if necessary, set working directory by "cd dir_name"
4) write "grunt.cmd build"

To create context menu "Grunt build" in Mac OS:


Settings -> External Tools -> +
Name: build
Group: Grunt
Program: grunt
Parameters: build
Working directory: $FileDir$

To create context menu "Grunt build" in Windows:


Settings -> External Tools -> +
Name: build
Group: Grunt
Program: grunt.cmd
Parameters: build
Working directory: $FileDir$

Now Grunt will rebuild our project, but it's not yet enough - names of some files will be changed and we'll
need to add these files to git (run "git add .").

To do this, I use grunt-exec. Run in apps folder:
npm install grunt-exec
then add to Gruntfile.js (before last '}' for instance):
grunt.loadNpmTasks('grunt-exec');
then create 'exec' task in grunt.initConfig:
exec: {
  git_add:{
    command: 'git add .',
    cwd: '<%= yeoman.dist %>'
  }
},
now, to run this task in each build, add this line in list of
grunt.registerTask('build',
section. It will look something like that:
  grunt.registerTask('build', [
    'clean:dist',
    'useminPrepare',
    'concurrent:dist',
    'concat',
    'copy',
    'cdnify',
    'ngmin',
    'cssmin',
    'uglify',
    'rev',
    'usemin',
    'exec'
  ]);
That's all! :)