Using key press event in Angular JS

It is a very common use case to catch event of certain key press, like the enter key for example. In this post, I am gonna write about how to handle key press event in Angular JS – using the so called “angular way” approach.

Problem statement:
Let’s define a problem statement to begin with. I have a form with one text field and a button next to it.

enter image description here

And then there was a directive…
This directive checks for keydown & keypress and checks the keycode, 13 in case of the enter key.

app.directive('EnterPressed', function() {
    return function(scope, element, attrs) {
        element.bind("keydown keypress", function(event) {
            if (event.which === 13) { // 13 is the code for enter
                scope.$apply(function() {
                    scope.$eval(attrs.myEnter);
                });
                event.preventDefault();
            }
        });
    };
});

and that’s about it. We are done.

Usage:

<div ng-app="" ng-controller="WierdController">
    <input type="text" enter-pressed="doSomething()">
    <button ng-click="doSomething()">Let's go!</button>
</div>

When to use ng-if vs ng-show/ng-hide in Angular JS

Both ng-if and ng-show/ng-hide directives, shows/hides/removes/add HTML to the DOM based on expression provided to the attribute. The approach they use to do this is different though.

ng-show – The element is shown or hidden by removing or adding the .ng-hide CSS class onto the element.

ng-if, on other hand differs from ng-show and ng-hide in that ng-if completely removes and recreates the element in the DOM rather than changing its visibility via the display CSS property.

You might notice a significant improvement in the responsiveness of your app if you repalce all your ng-show/ng-hide with ng-if. Because ng-show leaves the element on the DOM and all of its watch expressions and performance costs would still exists even though these elements are not viewable to the user.

References :

Validation summary with custom message for each field in Angular JS

It is a common requirement to list down all the validation errors of a form in one place, however when using Angular, this gets a little tricky to do it the “Angular Way”.

1. Showing validation summary of the form

<ul>
    <li ng-repeat="(key, errors) in form1.$error track by $index"> <strong>{{ key }}</strong> errors
        <ul>
            <li ng-repeat="e in errors">{{ e.$name }} has an error: <strong>{{ key }}</strong>.</li>
        </ul>
    </li>
</ul>

Screenshot : This can be demoed here – http://plnkr.co/edit/05oDTf?p=preview

enter image description here

2. Showing Errors next to input fields

<form name="userForm" ng-submit="submitForm()" novalidate>
 <div class="form-group" ng-class="{ 'has-error' : userForm.name.$invalid && !userForm.name.$pristine }">
   <label>Name</label>
   <input type="text" name="name" class="form-control" ng-model="user.name" required>
   <p ng-show="userForm.name.$invalid && !userForm.name.$pristine" class="help-block">You name is required.</p>
 </div>

3. Disabling the form submit button, if any of the form input fields are invalid.

<button type="submit" class="btn btn-primary" ng-disabled="userForm.$invalid">Submit</button>

References:

Using timeouts in Angular JS

Angular provides $timeout, a wrapper forwindow.setTimeout. To use $timeout you will need to inject it into your controller.

Syntax:

$timeout(fn[, delay][, invokeApply]);

Example:

var app = angular.module('myApp', []);

function Ctrl($scope, $timeout) {  
     $scope.message = "Hello";
     $timeout(function(){$scope.message = "Hello after 3 seconds";}, 3000);       
} 

Update : Found this really interesting thread on SO – What advantage is there in using the $timeout in Angular JS instead of window.setTimeout?

Hope this helps :)

PageDown Editor for Angular JS

I have always been fascinated by stackoverflow’s markdown/pagedown editor, So when I needed an text editor for a project(question & answer site) I have been working on lately in my spare time – QASK, I decided to go with a similar editor built for Angular JS.

Though there were many directives available for this, none of them were as complete and easy to use as angular-pagedown

Demo : http://plnkr.co/edit/9PHabp?p=preview

    <script data-require="angular.js@1.2.22" src="angular.js"></script>

    <script src="Markdown.Converter.js"></script>
    <script src="Markdown.Sanitizer.js"></script>
    <script src="Markdown.Editor.js"></script>

    <!-- The directive -->
    <script src="angular-pagedown.js"></script>

    <script src="script.js"></script>
  </head>

  <body ng-app="myApp">
    <div ng-controller="myController">
      <pagedown-editor content="data.content" 
                       help="showSomeHelp()" 
                       show-preview="false" 
                       insert-image="insertImage()">
      </pagedown-editor>
      <hr />
      <pagedown-viewer content="data.content"></pagedown-viewer>
    </div>
  </body>

</html>

A little change in css and here’s how my final page looks like :)

enter image description here

Continue reading “PageDown Editor for Angular JS”

How to pass values from ASP.NET MVC Razor Views to Angular Controller ?

I have been using a lot of AngularJs code on my razor views lately. On one of my page, I wanted to pass to my angular controller the model which is being passed to my razor view.

Can be done with the following code

ng-init="init(@JsonConvert.SerializeObject(Model))"

JsonConvert.SerializeObject helps you convert an object to a JSON string and you can use ng-init to pass and use this value in your controller.

How to conditionally add remove required attribute in Angular Js ?

Working on a form and its validation using AngularJs, I was using the “required” attribute to mark fields required. However there were few fields that should be required depending on input on other fields. This seemed little tricky at first, I also posted this as a question on SO.

Here is what I ended up using:

<input type="text" value="" 
  ng-model="details.Address" 
  ng-required="addressRequired" />

Simple and clean :)

Bootstrap Modal With AngularJs – The angular way

To use bootstrap with angular, one can make use of this library http://angular-ui.github.io/bootstrap/ from the angular ui team. Initally I had a little problem implementing this as there were many angular modal plugins available out there, but after using and testing each of them I have found this one to be pretty simple and straight forward to write.

Before you begin you will need to include this js file in your scripts

<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.11.0.js"></script>

and of course bootstrap.css

<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">

Next in your html you will need to define a script block like this

<script type="text/ng-template" id="myModalContent.html">
    <div class="modal-header">
        <h3 class="modal-title">Modal Header</h3>
    </div>
    <div class="modal-body">
         Hello world!
    </div>
    <div class="modal-footer">
        <button class="btn btn-primary" ng-click="ok()">OK</button>
        <button class="btn btn-warning" ng-click="cancel()">Cancel</button>
    </div>
</script>

Next you will need to add this piece of code after you define your controller

var ModalInstanceCtrl = function ($scope, $modalInstance) {

    $scope.ok = function () {
        $modalInstance.close();
    };

    $scope.cancel = function () {
        $modalInstance.dismiss('cancel');
    };
};

add a button in your html to trigger the modal to open, also add a click event to a function open() where we will open the bootstrap modal using the above controller.

<button class="btn btn-default" ng-click="open()">Open me!</button>

Reference : https://github.com/angular-ui/bootstrap/tree/master/src/modal

Ng Table with ASP.NET MVC WEB API and Angular js

Before reading this post, I recommend you to visit the ng-table official documentation and site and get the basic ng-table working.

Below is my api which accepts some grid params like

  • PageNumber
  • PageSize
  • SortColumn
  • SortOrder
  • SearchTerm

for simplicity I have not involved database in this example. This api method return a CustomerGridResult object, which has two properties first – Count i.e the count of the total records present and Data – the data to be returned after applying the above filters.

WEB API

// POST api/testapi
public CustomerGridResult Post(GridParams gridParam)
{
    var col = typeof(Customer).GetProperty(gridParam.SortColumn);
    var c = new List<Customer>();

    Customers =
        Customers.Where(
            m =>
            m.DisplayName.ToLower().Contains(gridParam.SearchTerm.ToLower())
        ).ToList();

    if(gridParam.SortOrder == "asc")
    {
        c = Customers
         .OrderBy(x => col.GetValue(x, null))
         .Skip((gridParam.PageNumber - 1) * gridParam.PageSize)
         .Take(gridParam.PageSize).ToList();
    }
    else
    {
        c = Customers
         .OrderByDescending(x => col.GetValue(x, null))
         .Skip((gridParam.PageNumber - 1) * gridParam.PageSize)
         .Take(gridParam.PageSize).ToList();    
    }

    return new CustomerGridResult
               {
                   Count = Customers.Count(),
                   Data = c
               };
}

HTML

<div data-ng-app="main" data-ng-controller="GridController">
     <table data-ng-table="tableParams">
        <tr data-ng-repeat="user in $data">
            <td data-title="'First Name'" data-sortable="'FirstName'">
                {{user.FirstName}}
            </td>
            <td data-title="'Last Name'" data-sortable="'LastName'">
                {{user.LastName}}
            </td>
            <td data-title="'Team'" data-sortable="'Company'">
                {{user.Company}}
            </td>
            <td data-title="'Pay'" data-sortable="'Salary'" class="currency">
                ${{user.Salary}}
            </td>
            <td data-title="'DOB'" data-sortable="'DateOfBirth'">
                {{user.DateOfBirth | date:'dd MMM yyyy'}}
            </td>
        </tr>
    </table>
</div>

AngularJs

var app = angular.module('main', ['ngTable', 'ngResource', 'ui.bootstrap']);

app.factory('customer', ['$http', function ($http) {
    return {
        getCustomers: function ($params) {
            return $http({
                headers: { 'Content-Type': 'application/json' },
                url: '/api/TestApi/',
                method: "POST",
                data: $params
            });
        }
        , getPlayersNames: function (searchTerm) {
            return $http.get('/api/testapi', { params: { searchTerm: searchTerm } });
        }
    };
} ]);

app.controller('GridController', ['$scope', '$http', 'ngTableParams', 'customer', function ($scope, $http, ngTableParams, customer) {

    // set defaults
    $scope.total = 5;
    $scope.page = 1;
    $scope.pageSize = 10;
    $scope.SortColumn = "FirstName";
    $scope.SortOrder = "asc";
    $scope.SearchTerm = "";

    $scope.tableParams = new ngTableParams(
        {
            page: $scope.page,
            count: $scope.total
        },
        {
            total: $scope.total, // length of data
            getData: function ($defer, params) {

                for (var i in params.sorting()) {
                    $scope.SortColumn = i;
                    $scope.SortOrder = params.sorting()[i];
                }

                var paramToPost = {
                    PageNumber: $scope.tableParams.page(),
                    PageSize: $scope.tableParams.count(),
                    SortColumn: $scope.SortColumn,
                    SortOrder: $scope.SortOrder,
                    SearchTerm: $scope.SearchTerm
                };

                customer.getCustomers(paramToPost)
                        .success(function (gridData) {
                            $defer.resolve(gridData.Data);
                            params.total(gridData.Count);
                        })
                        .error(function () {
                            alert("oops! something went wrong.");
                        });
            }
        }
    );
    // end of tableParams

    $scope.searchSubmit = function (val) {
        $scope.SearchTerm = val;
        $scope.tableParams.page(1);
        $scope.tableParams.reload();
    };

    $scope.clearSearch = function() {
        $scope.asyncSelected = "";
        $scope.searchSubmit("");
    };

    $scope.getLocation = function (val) {
        return customer.getPlayersNames(val)
            .then(function (res) {
                var players = [];
                angular.forEach(res.data, function (item) {
                    players.push(item.FirstName + ' ' + item.LastName);
                });
                console.log(players);
                return players;
            });
    };

} ]);