AngularJS directive for adding focus to an input box based on some other value

I needed a directive to enable focus in an input text element when some variable was set to true. The case was that the user would open up an accordion and the input element would appear. The mouse had to be placed inside the input element on show.

I found the following blog-article describing how to do this. It works just like a charm: http://www.angulartutorial.net/2014/04/angular-js-auto-focus-for-input-box.html

AngularJS logo

Create an AngularJS datefilter to properly display JSON date strings

Working with AngularJS often puts me in a situation where I need to deal with date strings in JSON returned from some .net server and looking like the following:

/Date(1297246301973)/

The string returned here represents the date in milliseconds and before you can use this date in your application, you need to convert it properly. Luckily, in AngularJS you can just create a custom filter like the following:

app.filter("dateFilter", function () {
    return function (item) {
        if (item != null) {
            return new Date(parseInt(item.substr(6)));
        }
        return "";
    };
});

You can then use the filter in the following way in your HTML:

    <tr ng-repeat="t in Result">
        <td>{{t.Date | dateFilter | date:"dd-MM-yyyy"}}</td>
    </tr>

You can see here that first I convert the JSON date string to a real date, and then I just use the regular date-filter in AngularJS in order to properly format it.

AngularJS logo

Creating a custom AngularJS filter to display items between values

Sometimes you need to filter out certain items in a list based on a numeric value. The following code accomplish that.

Put this into your controller:

    // Comparison filter
    $scope.inBetween = function (property, lowbound, highbound) {
        return function (item) {
            if (item[property] >= lowbound && item[property] <= highbound) return true;
            return false;
        }
    }

And then you just use the filter in the following way in your ng-repeat:

<tr ng-repeat="a in Result | filter: inBetween('accountnumber', 10100000, 15000000)">
</tr>
AngularJS logo

Filtering AngularJS items based on start and end date

There are cases where you would like to filter existing items in an AngularJS ng-repeat and only display the items where the date of the item is between any given start and end date. The following code accomplish that – please notice that I have only tested this with AngularJS version 1.2.22 and also notice that I use moment.js for the parsing of the date.

The following code is the filter – put this into a controller:

    // Daterange filter
    $scope.dateRangeFilter = function (property, startDate, endDate) {
        return function (item) {
            if (item[property] === null) return false;

            var itemDate = moment(item[property]);
            var s = moment(startDate, "DD-MM-YYYY");
            var e = moment(endDate, "DD-MM-YYYY");

            if (itemDate >= s && itemDate <= e) return true;
            return false;
        }
    }

And then – in your ng-repeat – you insert the following code:

    <tr ng-repeat="t in Result | filter: dateRangeFilter('itemDate', startDate, endDate)">
        <td>{{t.Name}}</td>
        <td>{{t.Adresse}}</td>
        <td>{{t.Phone}}</td>
        <td><a ng-href="mailto:{{t.Email1}}">{{t.Email1}}</a></td>
        <td>{{t.itemDate | dateFilter | date:"dd-MM-yyyy"}}</td>
    </tr>
AngularJS logo

Detecting route changes in AngularJS

Working with AngularJS there are cases where you want to execute code in a controller when the user clicks a link and the route changes.

In the following I’m triggering a new search based on querystring parameters.

function TenantsCtrl($scope, $http, $routeParams, $location, fetchFinancialService) {
    $scope.$on('$routeChangeSuccess', function () {
        if (typeof $routeParams.startDate === "undefined" || typeof $routeParams.endDate === "undefined") {
            $scope.startDate = "01-01-2000";
            $scope.endDate = "31-12-" + new Date().getFullYear();
        } else {
            $scope.startDate = $routeParams.startDate;
            $scope.endDate = $routeParams.endDate;
        }
    });

    fetchFinancialService.fetchTenants($scope, $http, $routeParams);
}

Please notice that it is the $routeChangeSuccess-event that I utilize for this purpose.

Executing code when all templates have been included – AngularJS

Sometimes, you want to be able to execute code when all the templates have been included in AngularJS using the ng-include attribute. Perhaps, you want to manipulate the inserted HTML…

AngularJS doesn’t provide a method to handle this out of the box, so instead you have to implement it yourself. This is done by utilizing the $includeContentLoaded event, which is fired each time one template has been loaded. Just like the following codesnippet:

scope.$on('$includeContentLoaded', function(event) {
    renderedcount++;
    if (renderedcount == itemmax) {
        // Crappy hack!
        $('.removeparent').unwrap();
        $('.removeparent').removeClass('removeparent');
    }
    console.log('another include was loaded', event.targetScope);
});

Don’t mind the “Crappy hack”-comment…This is just me telling myself, that I should probably refactor this code at some point…*ahem*….

But anyway, you subscribe to the $includeContentLoaded-event, and keep track of all the templates by using a simple counter. When all templates are loaded, you execute the code – which in this instance is jQuery to manipulate the inserted HTML.

It would be nice to have this feature in AngularJS itself – but unfortunately it isn’t. But this way, you can still get what you want…

Avoid the empty span-tag when using ng-include in AngularJS

Inserting HTML-templates via ng-include in AngularJS is a smart way to reuse code in your webapps. That way, you can have code in a separate file or in a separate block of code in the same file, and then insert the block of code whenever you need it.

Normally, you would have a block of code – just like the following:

<script type="text/ng-template" id="tpl1">
    <img src="img/pizza-pic1.jpg" alt="">
    <a href="#" class="title food-and-drink-cat h-50">
        <h3>Pizzasten</h3>
    </a>
</script>

You then insert the block of code in the following way:

<div ng-include="'tpl1'" ng-class="article.class">
</div>

This code will insert the template into the div-element. However, there is one small problem. If you inspect the result in Chrome Developer Tools, you will notice that AngularJS automatically inserts empty spans. Something like the following:

<span class="ng-scope">
</span>

AngularJS inserts this code in order to keep track of the current scope. However, I think it’s a mistake, and sometimes the empty span-tag messes with the layout of the page. Therefore, I investigated why this happens.

First of all, you can safely remove the empty span-tag. AngularJS doesn’t barf at you. Second, removing the empty span-tag is very easy. You just have to remove all spacing between the script-tag and the HTML. Just like so:

<script type="text/ng-template" id="tpl1"><img src="img/pizza-pic1.jpg" alt=""><a href="#" class="title food-and-drink-cat h-50"><h3>Pizzasten</h3></a></script>

This removes the empty span-tag – without consequences for AngularJS. I know that this solution isn’t pretty – I like to have my code formatted nice and pretty – but it works, and this is the important part.

AngularJS: Filter your data

Sometimes, you need to filter your data in order to get to one or more specific records in your JSON-object. You probably know about the possibility of adding | filter: {expression} to your ng-repeat lists, just like so:

 <td>
  <xsl:attribute name="ng-repeat">
    <xsl:text>orderlinedescription in line.OrderLineDescriptions | filter:'</xsl:text><xsl:value-of select="sc:language()"></xsl:value-of><xsl:text>'</xsl:text>
  </xsl:attribute>
  {{orderlinedescription.Description}}
</td>

However, I didn’t know that it is also possible to filter data in the controller – before sending data to the view. If you want to use this method, you have to inject $filter into your controller.

Just like the following example here where I search for a particular photo by looking up the ID:

function EditPhotoCtrl($scope, $location, $routeParams, globals, messageService, $filter) {
  var image = [];

  $scope.getServiceOrderByOrderNumber($scope, globals, $routeParams.ordernumber, function(status, result) {
    $scope.serviceorder = result;

    // Get the correct photo
    image = ($filter('filter')(result.Photos, {Id: $routeParams.Id}));
    $scope.image = image[0];
    $scope.$apply();
  });
}

What happens is that $filter generates a new array consisting of the found results. In the example I just need one record, so I select the first one. But you could also generate a new list consisting of the found results.

That’s it. AngularJS continues to amaze me. :-)

AngularJS tips and tricks – nesting form elements

A very small trick indeed, but very important to me. I was having trouble incorporating my AngularJS-application into a .net environment. ASP.net web forms require a form tag to work, and my application had a form already. I was validating this existing form by using AngularJS’s ability to automatically keeping track of the forms valid/invalid state via $scope.mainform.$invalid. This feature in AngularJS uses the name of the form as a reference – but this creates problems in asp.net.

I tried combining the forms, so that the asp.net form tag would work together with my AngularJS-form tag. Something like this:

<form method="post" name="mainform" runat="server" id="mainform" ng-controller="MainCntl">

However, this didn’t work. The reason is that asp.net removes the name part of the form-tag in order to ensure strict XHTML-compliance. So now my form-validation didn’t work any longer.

You can adjust this setting in web.config, so that asp.net will allow legacy or transitional rendering, but you can also just use a neat feature in AngularJS, which allows you to nest forms.

Therefore, you can write this instead:

<form runat="server">
  <ng-form name="mainform" id="mainform" ng-controller="MainCntl">

The ng-form is a special AngularJS form alias allowing you to have multiple forms in your HTML-document. This is described in more detail in the reference: http://docs-angularjs-org-dev.appspot.com/api/ng.directive:form

Maybe a bit of a hack, but it works….

AngularJS tips and tricks – broadcast online and offline status

In continuing my experiments with AngularJS, I often stumble upon new challenges, I haven’t met before. One of these challenges was to broadcast the online and offline status of the browser.

I wanted to be able to hide different parts of the gui, if the user wasn’t online. In order to do that, I had to create a centralized variable, that would notify the gui whenever changes occurred. I did this by applying the following code in my app:

    app.run(function($rootScope) {
      // console.log("online:" + navigator.onLine);

      $rootScope.online = navigator.onLine ? 'online' : 'offline';
      $rootScope.$apply();

      if (window.addEventListener) {
        window.addEventListener("online", function() {
          $rootScope.online = "online";
          $rootScope.$apply();
        }, true);
        window.addEventListener("offline", function() {
          $rootScope.online = "offline";
          $rootScope.$apply();
        }, true);
      } else {
        document.body.ononline = function() {
          $rootScope.online = "online";
          $rootScope.$apply();
        };
        document.body.onoffline = function() {
          $rootScope.online = "offline";
          $rootScope.$apply();
        };
      }
    });

As you can see, the function adds an event listener that will automatically update the $rootScope.online variable. Now, I can just use this variable in my HTML – like so:

<li ng-show="online == 'online'"><i class="icon-lock dark"></i><asp:LoginStatus ID="LoginStatus2" runat="server" LogoutText="Signout" LogoutPageUrl="~/Login.aspx" LoginText="Signout" LogoutAction="Redirect" /></li>

Works like a charm :-)