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

3/23/17

Using Animation Module With Angularjs

Hi dear dudes & dudesses!
Today we gonna speak about angular animate module,
Yes, in case you didn't noticed , alongside with "controller", "directive" and "filter" angular recipes, exists another one - "animate".
You can use it by adding "ngAnimate" module to your modules:


angular.
  module('yourApp', [
    'ngAnimate',
    ...
  ]);


Until now I didn't have a chance to really use it in some of my projects, actually I have only read about what it suppose to do in great phonecat tutorial - the "first steps" tutorial that angular guys made for us.

Challenge

So current challenge is to try to animate the dropdown directive I have created in one of my old posts, in a way to will expand when open and collapse when closed.

Get Started

Since angular animations triggered when one of angular events enter, leave, add and move takes place - we need to modify our components in a way it will use ng-if directive which cuases enter and leave events to fire:
Instead of hide/show dropdown menu by adding and remove 'open' class on its parent I will make dropdowMmenu template to listen to its parent isOpen property...


angular.module('ui.components',['ngAnimate'])
.component('dropdown',{ 
  controller:function($element,$document,$animate, $timeout) {
     $element.addClass('dropdown');
     
     var menu = $element.find('dropdown-menu')[0];
   
     $document.on('click',(evt)=>{
        if(!$element.find('dropdown-toggle')[0].contains(evt.target)){
          this.close() 
        }
     })
 
    this.close=() => {
       $timeout(()=> this.isOpen = false,1); 

    }
    this.open=() => {
       $timeout(()=> this.isOpen = true,1);
    }    
  }
})
.component('dropdownToggle',{
  require: {
    parent: '^^dropdown' 
  },
  controller:function($element) {
    $element.addClass('dropdown-toggle');
    $element.on('click',($event)=>{
       this.parent.isOpen ? this.parent.close() : this.parent.open();
    })     
  }
}) 
.component('dropdownMenu',{
  require: {
    parent: '^^dropdown' 
  },
  transclude:true,
  template:'<div class="dropdown-menu" ng-if="$ctrl.parent.isOpen" ng-transclude></div>'//IMPORTANT: hide menu with ng-if directive
})

Important!
Since now we use ng-if to hide and show menu we must remove display:none rule from CSS of .dropdown-menu class.

Animation Module

The time for use animation recipe came.


.animation('.fold-animation', ['$animateCss', function($animateCss) {
  return {
    enter: function(element, doneFn) {
      var height = element[0].offsetHeight;
      return $animateCss(element, {
        from: { height:'0px' },
        to: { height:height + 'px' },
        duration:0.25
      });
    },
    leave: function(element, doneFn) {
      var height = element[0].offsetHeight;
      return $animateCss(element, {
        from: { height:height + 'px' },
        to: { height:'0px' },
        duration:0.25
      });
    }    
  }
}]);

I copied this code from this angular example with slight modifications
See full code on this plunker.
Also you can read more about angular animations in their accurate docs

2/22/17

Building Drop Down Menu Using Angular1 Component

Challenge

To build drop down menu like angular-ui-bootstrap with angularjs using "component":

Behaviors

  1. Menu toggle button Should open hidden menu when clicked, and to hide the menu if it open
  2. Menu Should Close itself when users clicks on some other place
  3. If disabled - should not show anything when clicked

Step 1 - CSS & HTML

For sake of simplicity i will copied styles from bootstrap framework:


.open > .dropdown-menu {
    display: block;
}
.dropdown-menu {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 1000;
    display: none;
    float: left;
    min-width: 160px;
    padding: 5px 0;
    margin: 2px 0 0;
    font-size: 14px;
    text-align: left;
    list-style: none;
    background-color: #fff;
    -webkit-background-clip: padding-box;
    background-clip: padding-box;
    border: 1px solid #ccc;
    border: 1px solid rgba(0, 0, 0, .15);
    border-radius: 4px;
    -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
    box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
}

and the html will be following:

    <dropdown>
        <dropdown-toggle>
        <a href="">Dropdown <span class="caret"></span>
        </a>
        </dropdown-toggle>
        <dropdown-menu>
        <li>
            <a href="" ng-click="$ctrl.sayHi()">Action</a>
        </li>
        <li>
            <a href="">Another action</a>
        </li>
        <li>
            <a href="">Something else here</a>
        </li>
        <li role="separator" class="divider"></li>
        <li>
            <a href="">Separated link</a>
        </li>
        </dropdown-menu>
    </dropdown>

inside 'dropdown-toggle' container user should be able to place html of 'a' tag or button (according to his needs)
inside 'dropdown-menu' container user should be able to place html of menu items - there he should be able to attach methods from controller - like sayHi

dividing to components

I decided to divide this component to three different components:
The dropdown Parent component and dropdownToggle and dropdownMenu child components
That way logic will be splitted to its specific component

dropdown component

Should contain close and open methods


angular.module('drop',[])
.component('dropdown',{ 
  controller:function($element,$document) {
    $element.addClass('dropdown');//
    this.close=() => {
      $element.removeClass('open');
      this.isOpen = false;
    }
    this.open=() => {
      $element.addClass('open');
      this.isOpen = true;
    }    
  }
})

The mechanic here is that menu is visible when the main container (dropdown element) have "dropdown" and "open" classes, that why close and open are adding and removing 'open' classes accordingly

dropdownToggle component

Is the element that must handle the clicks and activate its parent 'open' and 'close' methods;
For be able to access its parent methods i'm using require option inside component definition:


.component('dropdownToggle',{
  require: {//<--USING REQUIRE OPTION
    parent: '^^dropdown' 
  },
  controller:function($element) {
    $element.addClass('dropdown-toggle');
    
    $element.on('click',($event)=>{
       this.parent.isOpen ? this.parent.close() : this.parent.open();
    })     
  }
})

click outside behavior

For be able to close the menu when user clicks somewhere else on the screen i'm taking advantage of $document service:
dropdown.js



 ...
  $document.on('click',(evt)=>{
    if(!$element.find('dropdown-toggle')[0].contains(evt.target)){
      this.close() 
    }
  })
...

You can see running code on this punker

2/9/17

Lazy loading - Is it possible in Angular1

What Is Lazy Loading?

Single page application, as you can see from its name - is built with one single page. In this single page javascript loads (when needed) various views. Each view may have its won modules, components, and other javascript logic inside them. Since it may happen that site user will not access some views at all - it is good practice to avoid load then every time the app accessed, istead - the ideal tactic is to load those javascript parts only when this specific view was called. But is there some way to do it in angular1?

The Answer is Yes

Actually there is a way to implement lazy loading with angularjs, for example by using ocLazyLoad angular module. You can read here how to load entire module or only some directive or service or how to load some state from a script!

Experiment

As usual lets show how to use lazy loading on some of our old good projects - the angular phonecat.
Lets start with download or clone the project to some local directory.

git clone https://github.com/angular/angular-phonecat.git


After installing dependencies (npm i) lets register the ocLazyLoad module to bower.json:

bower i oclazyload -D

After install and place reference on index.html file, we must register lazy load module in root application module:


'use strict';

// Define the `phonecatApp` module
angular.module('phonecatApp', [
  'ngAnimate',
  'ngRoute',
  'core',
  'phoneDetail',
  'phoneList',
  'oc.lazyLoad'
]);

Create About Page

For testing lazy load feature lets create "about" module which will contain "component","config","module" and "template" files

we will rester only "config" and "module" references on the index.html
Lets add about module to our root "phonecatApp" module:

angular.module('phonecatApp', [
  'ngAnimate',
  'ngRoute',
  'core',
  'phoneDetail',
  'phoneList',
  'oc.lazyLoad',
  'about' 
]);

We didn't put the reference to about.component fie on the index.html on the purpose.
The "component" file will load with lazy load module after user will access "about" route.


angular.module('about').
    config(['$locationProvider', '$routeProvider',
        function config($locationProvider, $routeProvider) {
            $routeProvider.
                when('/about', {
                    template: '<about-comp></about-comp>',
                    resolve: ['$ocLazyLoad', function ($ocLazyLoad) {
                        return $ocLazyLoad.load([{
                            files: ['about/about.component.js']
                        }])
                    }]
                });
        }
    ]);

Lets try to navigate to /about url
Wow! Now you can see the about component is loaded!
Thanks for reading

12/25/16

Add Ajax Request Indicator

Some times it is good practice to notify user that due to his actions (like sending the contact us form) some ajax request been sent and it is been processed. If you not reckon with nprogress angular lib yet, this good opportunity to introduce it... This library is angular implementation of nprogress jquery plugin, and its showing very nice loading bar on top of you application:

How to make it shown only if some ajax request made?

If you don't want to track any other requests like js and css files, only data requests to backend, it may be done by watching pendingRequests property of $http angular service:


myApp.run(function ($rootScope, $http, ngProgressFactory) {
        $rootScope.progrs = ngProgressFactory.createInstance();
        var progresstarted, progressbar = $rootScope.progrs;
        $rootScope.$watch(function() {
            var onlyJsonRequests = $http.pendingRequests.filter(function(r){return r.url.match(/json/g);});
            return onlyJsonRequests.length;
        }, function(n) {
             
            if(n>0 && !progresstarted) {
             
                progresstarted = true; 
                progressbar.setColor('blue');
                progressbar.setParent(document.getElementById('mainContainer')); //set a custom container, otherwise will be attached to "body"
               
                progressbar.start();
            } else if(n===0 && progresstarted) { 
                progressbar.complete(); 
                progresstarted = false;  
            }
        }) 
}) 

Notice that progress bar will be shown only if requests with url property that contains /json/ are sent

Provider is Better

Currently we have a lot of logic inside our run section.
One thing that can make the code more accurate is to move the progress logic to provider recipy:


myApp.provider('progress', function($provide, $injector) {
  this.setPattern = function(pattern) {
    this.pattern = new RegExp(pattern,'g');
  };
  this.setContainer = function(container) {
    this.container = container;
  };
  this.setColor = function(color) {
    this.color = color;
  };
  //will fire when used in one of recipies like "controller" or "directive" or "run"
  this.$get = function ($rootScope, $http, ngProgressFactory) {
        var that = this;
        $rootScope.progrs = ngProgressFactory.createInstance();
        var progresstarted, progressbar = $rootScope.progrs;
        $rootScope.$watch(function() {
            var onlyJsonRequests = $http.pendingRequests.filter(function(r){
              return r.url.match(that.pattern);
            });
            return onlyJsonRequests.length;
        }, function(n) {
            if(n>0 && !progresstarted) {
                progresstarted = true; 
                progressbar.setColor(that.color);
               progressbar.setParent(document.getElementById(that.container)); 
                progressbar.start();
            } else if(n===0 && progresstarted) { 
                progressbar.complete(); 
                progresstarted = false;  
            }
        }) 
        return 'watching'
   };
});

Now we can set all the custom settings inside config section:

myApp.config(function(progressProvider) {
  progressProvider.setContainer('mainContainer');
  progressProvider.setPattern('json');
  progressProvider.setColor('blue');

Now inside our run section we can leave only progress injection:

myApp.run(function (progress) {
}) 

If you want to see the full code - look on this plnkr

11/27/16

Export angularjs module

What are submodules?

When you writing angularjs application it is a good pattern to divide your app to submodules which are included into the main module as following:


angular.module('mainApp',['myApp.version','myApp.view1','myApp.view2'])

So it will be easy for testing or develop various parts of the application without each part need to knowing about other parts.
The directory structure of application usually place each submodule into its own directory:

.
├── view1
│   ├── view1.ts
│   └── view1.html


One of submodule "main" files contains angular module declaration:

    angular.module('myApp.view1', [
      'myApp.version.interpolate-filter',
      'myApp.version.version-directive'
    ])


How to import submodule into main typescript file?

It appears you can import code file which have no "export" syntax at all:


  //NO EXPORT AT ALL!
    angular.module('myApp.view1', ['ngRoute'])

    .config(['$routeProvider', ($routeProvider: angular.route.IRouteProvider) => {
      $routeProvider.when('/view1', {
        templateUrl: 'view1/view1.html',
        controller: 'View1Ctrl'
      });
    }])

    .controller('View1Ctrl', [() => {
    }]); 

Now in the main file - you can still use import syntax to include this code
app.ts

import './view2/view2';


Hope you have fun reading...

11/17/16

Organizing Your Angularjs Typescript Project

In one of my previous posts i found how to compile typescript files to javascript and i was very happy with myself because of my success. Since then i had little more experience with typescript. And now i have a few more points to share:

Using Import In Your Typescript Project

I have noticed many angularjs and angular2 projects using Import syntax. That way they not need to compile each file separately, instead you can compile only main file while all other files included in it (like in less or sass)
For exapmle :
Instead put all the things in your JS app.js file


//OOOOOLD STUFF.. BLAHHH
angular.module('myApp', [
  'ngRoute',
  'myApp.view1',
  'myApp.view2',
  'myApp.version'
]).
config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) {
  $locationProvider.hashPrefix('!');

  $routeProvider.otherwise({redirectTo: '/view1'});
}]);

In typescript, you can move the config logic to its own separate config.ts file:

 export function config($locationProvider: ng.ILocationProvider, $routeProvider: angular.route.IRouteProvider) {
    $locationProvider.hashPrefix('!');
    $routeProvider.otherwise({redirectTo: '/view1'});
  };



and import it to app.ts main file:

/// 
import { config } from './config';///<--HERE
module angularSeed {
'use strict';

  // Declare app level module which depends on views, and components
  angular.module('myApp', [
    'ngRoute',
    'myApp.view1',
    'myApp.view2',
    'myApp.version'
  ]).config(config)

}
 

Import Is ES6 Syntax (Not Typescript!)

So, if you need to transpile your code either from ES6 and from typescript - i cannot think about other tool than webpack.

Weeeebpaaaack....
Webpack supports ES6 natively and for get it to compile typescript you only need to include ts-loader:
 
var webpack = require('webpack');
var path = require('path');
module.exports = {
    context: path.join( __dirname, '/app'),
    entry: 'app.ts',
    output: {
        filename: 'app.js',
        path: path.join( __dirname, '/app')
    },
    resolve: {
        root: path.join( __dirname, '/app'),
        extensions: ['', '.ts', '.webpack.js', '.web.js', '.js']
    },
    module: {
        loaders: [//<-- TYPESCRIPT, COOOL
        // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
            { test: /\.ts?$/, loader: 'ts-loader' }
        ]
    }
}

Of course you need to include this webpack.config.js file in your project And update package.json with too more entries:
 
    "typings": "^0.8.1",
    "webpack": "1.13.1",
 

Build The Project

Since we using webpack now for build the project, the command to compile will be:


node_modules/.bin/webpack
 

Hope you have fun reading...
Visit my cool REPO

10/22/16

What is bindToController using for in angularjs?

If you start a new plunker by going to new -> angularjs -> 1.5.x you will get a starter template that attaches "name" property to the controller :


app.controller('MainCtrl', function($scope) {
  $scope.name = 'World';
});

and displays it in the template :

  <body ng-controller="MainCtrl">
    <p>Hello {{name}}!</p>
  </body>

controllerAs

Actually the more recommended way (look at john papa style guide) to attach some properties and methods to the controller - is by using controllerAs syntax:


app.controller('MainCtrl', function() {
  var ctrl = this;
  ctrl.name = 'World';
});

and the template should now be:

  <body ng-controller="MainCtrl as ctrl">
    <p>Hello {{ctrl.name}}!</p>
  </body>

(ctrl is recommended name to use in the controllerAs syntax by angular guys)

The problem with directives

When writing directives which has their own controllers we may use the controllerAs syntax as well:


app.directive('doSomething', doSomething);
function doSomething() {
  return {
    restrict: 'E',
    replace:true,
    scope:{}, //<--isolated scope
    template:'<button ng-click="ctrl.clickBtn()">click here</button>',
    controllerAs:'ctrl',
    controller: function() {
      var ctrl = this;
      ctrl.name = 'koko';
      ctrl.clickBtn = function() { 
        alert(ctrl.name);  //<--will alert 'koko'
      }
    }
  };
}

But what happens when we need the 'name' property to be passed from the outer scope, like below?

  ...
  scope:{name:'='}
  ...

How we make the controller to know about its new property?
Thats what the bindToController setting in the directive configuration are responsible for - to bind the outer scope members to the controller of directive:

 app.directive('doSomething', doSomething);
 function doSomething() {
  return {
    restrict: 'E',
    replace:true,
    scope: {
      name: '='
    },
    template:'<button ng-click="ctrl.clickBtn()">click here</button>',
    controllerAs:'ctrl',
    bindToController: true, //<-- HERE
    controller: function() {
      var ctrl = this;
      ctrl.clickBtn = function() { 
        alert(ctrl.name);   //<--will alert 'World'
      }
    }
  };
}


full code at this plunkr

Components

Since angular 1.5 introduced the Component method - we can take advantage of using the components to achieve the similar behavior:


app.component('somethingNewer', {
  bindings: {
    name: '='
  },
  template:'<button ng-click="ctrl.clickBtn()">click here</button>',
  controllerAs:'ctrl',
  controller: function() {
      var ctrl = this;
      ctrl.clickBtn = function() { 
        alert(ctrl.name); 
      }
  }
});

This code doing the same thing, but it shorter and its more like angular2
P.S.
controllerAs property if not specified - defined by default as $ctrl.
Hope you have fun reading...

10/10/16

Making Angular1 Seed Project To Work With Typescript

challenge

Since typescript became more and more popular those days i decided to try and take the following challenge: To convert this angular1. seed-project from old fashioned ES5 to Typescript.
I have very little experience with typescript, and that why it is interesting. This project consist only from two basic views so it seems to be not so hard...

Typings

First we need to add typings to our project. What are those typings?
Typings are dependencies as angular or angular-routes libraries which translated to typescript
First step we need to add typescript & typings dependencies to package.json file


"typescript": "^1.8.10",
"typings": "^0.8.1",

Lets also add typings.json file which has list of Definitions of libraries we use in project

{
  "ambientDependencies": {
    "angular": "registry:dt/angular#1.5.0+20160412133217",
    "angular-route": "registry:dt/angular-route#1.3.0+20160317120654",
    "jquery": "registry:dt/jquery#1.10.0+20160417213236"
  }
}

After adding those files lets update again the package.json file so it will install typings after installing all dependencies:

...
  "scripts": {
    "postinstall": "bower install && typings install",
    ...
   }
...

Lets check - after runing npm i command the "typings" directory should appear:

Compiling

The compile command of typescript you need to call for convert your ts files into js is "tsc -p ./".
tsc takes other options of how to compile typescript from tsconfig.json file:


{
  "compileOnSave": false,
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5"
  },
  "exclude": [
    "bower_components",
    "node_modules",
    "typings"
  ]
}

It is convenient to configure package.json "scripts" zone so you can run the tsc with npm:
npm run build

..
  "scripts": {
   "build": "tsc -p ./",
    ...
   }
...

Change the code to typescript

Now you can start to convert the project to typescipt:
First - lets start with view1.js file , by rename it to view1.ts


/// <reference path="../../typings/main/ambient/jquery/index.d.ts" />
/// <reference path="../../typings/main/ambient/angular/index.d.ts" />
/// <reference path="../../typings/main/ambient/angular-route/index.d.ts" />
angular.module('myApp.view1', ['ngRoute'])

.config(['$routeProvider', ($routeProvider: angular.route.IRouteProvider) => {
  $routeProvider.when('/view1', {
    templateUrl: 'view1/view1.html',
    controller: 'View1Ctrl'
  });
}])

.controller('View1Ctrl', [() => {

}]); 

See that after running "npm run build" the view1.js file generates!

see full code here: repo
Thanks for reading

9/29/15

is it possible to track your visitors individual behavior?

The short answer is "Yes, sure", but there are lot of options how to do it. I will talk now about how to track behavior using google analytics.
I guess  everyone knows how to implement google analytics at your website  - to create account at google analytics and to paste the code snippet like:


    var _gaq = _gaq || [];
    _gaq.push(['_setAccount', 'your-account-num']);
    _gaq.push(['_trackPageview']);

    (function() {
        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
    })();


This great tool of google makes you able to analyze your website traffic (like getting reports) 

 








without writing any code!
 
The problem with tracking individual user's behavior is that you need analytics service to know the id/name of specific user. Thats where angularticks library can be usefull.
This lybrary enables you easily fire events with data you choose to send:

 var eventData = {  
  category: 'motorcycle-details',
  label: user+' viewed '+$routeParams.productname +' motorcycle'
 };
 $analytics.eventTrack('viewing-motorcycle-details', eventData);

If user name passed through the event parameres the reports will show results with his behavior:



 

9/19/15

how to make google to index your angular single page app

The problem with angular single page app (which uses router module) is that google crawler cannot access route view dynamic content.
When processing such pages (like mysite.com/#/product/zzr1400) google crawler sees only template with "{{something should be here}}" tags.

I have created this site for experimenting purposes.
According to little research I did (see this grate tutorial and here) for make your site to be indexed by google you need to serve crawler bot static HTML snapshots made from your dynamic views.

Steps

For doing it you need to perform following steps:
1. place following tag in the head of your index page:

<meta name="fragment" content="!">

2. make sure your router configured to use "!" or html5 mode

  $locationProvider.html5Mode(true);
  $locationProvider.hashPrefix('!');

This tells crawler to access the site using "?_escaped_fragment_=" quesry string.
3. configure your server or use some online services like Prerender.io or brombone to serve static html snapshots of your content.

How to check you pages are cached for crawler?

If you using prerender.io online service:
Access your dashboard:

You should see the table of your pages in the "cached pages" section.

6/15/15

Interesting thing about $watch

I guess everybody knows how to make angular to respond on the changes made to scope variables- to add some callback to "$watch" , something like this:

scope.$watch('name', function(newValue, oldValue) {
  scope.counter = scope.counter + 1;
});

But the cool thing that $watch can listen and respond to changes of functions which return something too. For example:

 $scope.tabs=[{active:true, name:'first'},{active:false, name:'second'},{active:false, name:'third'}}];
 $scope.$watch(function(){ return $scope.tabs.filter(function(t){ return t.active; })[0]; },function(tab){
   $scope.$broadcast('tab-selected',{name:tab.name})
 },true)

5/13/15

Building & testing user-menu directive part 1

Part1


scenario

Consider the following scenario:
At the right of top navbar of the application screen, user should see his username   and "sign out" link while he is loggedin:

loggedin view


and when he is  not logged in - "signin" and "singnup" links should be displayed.

not-loggedin view



directive

Lets build simple directive user-menu  which will be placed into top navbar of our page:


<ul>
    <user-menu></user-menu>
</ul>


usermenu.js

angular.module("myApp")
.directive('userMenu', function () {
    return {
        restrict: 'E', 
        templateUrl: 'usermenuTpl.html',
        controller: function ($scope, userSvc) {
            if (userSvc.isAuthenticated()) {
                $scope.profile = userSvc.getProfile(); 

           }
        }
    }
})



userMenuTpl.html

<ul class="nav navbar-nav navbar-right" ng-if="profile">
  <li>
    <a href="/profile"><i class="glyphicon glyphicon-user">
      </i> {{profile.username}}
    </a>
  </li>  
  <li style="border-left:1px solid #fff">

     <a href="/link-to-logout">Sign out</a>  
  </li>
</ul>

<ul class="nav navbar-nav navbar-right" ng-if="!profile">
  <li>
    <a href  ng-click="open()">Sign in</a>  
  </li>  
  <li>
    <a style="border-left:1px solid #fff" href="/link-to-login">Sign Up</a>  
  </li>
</ul>



All things are simple - directive consists from 2 templates, and display each wether "profile" property is set or not.
"Profile" property will came from some User service (which usually should access to DB, but for being simple lets fake it)


userSvc.js

angular.module("myApp")
        .factory('userSvc', function () {
            return {
             isAuthenticated: function () {
                return true;                
             },
             getProfile: function () {
                return {username: 'Shlomo'};
             }
           }
        })



for testing manually lets see what happens when we changing code of isAuthenticated method so it returns false. Yes - we see signin & signup links.
So far so good
Hope you have fun reading

Part 2

5/5/15

Interesting thing about ui router "resolve"

What is resolve

Many angluar applications use  ui.router. This plugin makes the job of changing views easier and code responsible for view managing - more accurate. Also it makes the view accessible from the  url (deep linking) - which means application users can bookmark the certain view or send it by email.

This post is about one interesting feature of the ui.router (and also ng-router) - the resolve.
You can pass some data to the view when it initiated, by putting the service call into the "resolve" block:


var List  = ['itemsSvc', function(itemsSvc){
  return itemsSvc.get();
}],

$stateProvider
.state('app.items', {
  url: '/items',  views: {
    'tabpanel': {
      templateUrl: 'views/items.html',      
      controller: 'itemsCtrl'    
     }
  },
  resolve:{
    items: List
  }
})



after loading the resolve item can be accessed inside the controller - as any other of injectables:


angular.module('My')
  .controller('ItemsCtrl', function ($scope, items) {
    $scope.list =  items;
    ...


Using resolve ensures that view will not be rendered until the request specified inside the resolve will return back.

You can update the collection cached inside the resolve.
Consider you have following UX scenario:
The "add item" form is located on different  child view:


var List  = ['itemsSvc', function(itemsSvc){
  return itemsSvc.get();
}],

$stateProvider
.state('app.items', {
  url: '/items',  views: {
    'tabpanel': {
      templateUrl: 'views/items.html', 
      controller: 'itemsCtrl'    
    }
  }, 
  resolve:{
    items: List
  }
})
.state('app.items.new', {
  url: '/new',  views: {
    'details@app': {
      templateUrl: 'views/itemForm.html', 
      controller: 'itemCtrl'    
    }
  }
})


After successfully adding new item user should be redirected to the parent list view. But how to make the list view to show the newly added item?

You can do it by updating items collection you get from resolve:


angular.module('My')
  .controller('AddEditItemCtrl', function ($scope, items) {
   $scope.list =  items;
   $scope.add=function(e){

      $scope.newItem = { Id: '111', name:'some item'};
      $scope.list.push(angular.copy($scope.newItem));
      $state.go('app.items')
   };
};


After redirecting to "items" view you will see the new items is already here!

4/30/15

Cool Trick For Unit Testing The Angular Controller

Almost always when writing unit tests for controller we need somehow to handle the fact that controller calls to some service/factory.

Lets look at following controller:



angular.module('siteApp')
        .controller('RegisterCtrl', function (
        $scope,
         auth,
         $location,
         $interval) {

            $scope.user = {};
            $scope.messages = [];


            var success = function(){
                $scope.messages.push({
                    type:'success',
                    text:'Congratulations,You are registered user!'
                });             
                $interval(function(){$location.path('/login')},6000,1)
            }, error =function(){
                $scope.messages.push({
                   type:'danger',
                   text:'error occurred during registration'
                });                
            };

            $scope.save = function(){
                auth.register($scope.user).then(success, error);         
            }

  });



The controller uses "auth" service and its "register" method, which sending a request.
We can mock the request with $httpBackend service which test if request is sended.


it('after clicking "save" some request should be sent', function () {
  initCtrl()
  $httpBackend.expectPOST('/api/register').respond(200, {});
  $scope.save();
  $httpBackend.flush();
});


Since we trying now to test the logic of the controller (not the service)- I think better practice is to mock the  service method and only test if it been called with proper parameters:



it('after clicking "save" auth.register should call', function () {

    initCtrl()
    spyOn(auth, 'register').andReturn($q.when()); 
    $scope.save();
    expect(auth.register).toHaveBeenCalledWith($scope.user);

    $scope.$digest();   //important!!!
    expect($scope.messages.length).toBe(1)


});


Notice that for testing the messages array you need 

  $scope.$digest(); 

statement for update the scope.

Hope you have fun reading...


10/23/13

Shortened text directive - angularjs

 In this artricle we will try to create angularjs directive which  will display only first 10 characters of element text and link  that opens the whole text when user clicked on it.

Before clicking: 

After clicking: 


To simplify the example i'm using the code of angular phonecat tuttorial.
In the example we need to display the snippet  property of phone object

html markup:
 
    
 
Directive declaration:
 
/* app\js\directives.js */
var phonecatDirectives = angular.module('phonecatDirectives', []);
phonecatDirectives.directive('zippy', function () { 
    
    return {
      restrict: "E",
      replace: true,
      transclude: false,
      scope: { title:'@zippyTitle' },
      template: '
{{title}}
' , // The linking function will add behavior to the template link: function(scope, element, attrs) { var opened=true; scope.$watch('title',function(newVal, oldVal){ element.html(scope.title.substr(0,10)+' ...'); // Title element var title = angular.element(element.children()[0]), // Opened / closed state opened = true; // Clicking on title should open/close the zippy title.on('click', toggle); }) // Toggle the closed/opened state function toggle() { if(opened) element.text(scope.title) opened = !opened; //element.removeClass(opened ? 'closed' : 'opened'); //element.addClass(opened ? 'opened' : 'closed'); } } }; });

Getting started with docker

It is very simple to get started usig docker. All you need to do-is download the docker desktop for your system Once you get docker syste...