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 :-)

AngularJS tips and tricks

AngularJS logo
Are you working with AngularJS? Have you tried using the “documentation”? Have you banged your head into your keyboard trying to find out how seemingly simple methods works?

Yeah, I’ve been there. My trip into AngularJS has been filled with obstacles and trying to learn AngularJS from scratch is – in my humble opinion – very difficult. I find that AngularJS has a very steep learning curve and it’s probably mostly due to the fact that I’m not used to working with a Model-View framework. That being said, the documentation is not very¬†user-friendly, and you find yourself using Google to look up examples and forum posts! I owe the community a lot for providing me with the necessary insight.

On this page I will try to sum up some of the things that I found was difficult to grasp. It is primarily for my own understanding and reference – I’m glad if you can use it also.

I will be adding to the page whenever I make new discoveries.

Remember to apply changes

In my attempt to create an app that utilizes IndexedDB, I quickly ran into the problem of asynchronous updates. My AngularJS-app would render all controls long before my queries was done. The result: no data!

Normally, AngularJS is able to watch variables for any changes. But not if the changes are being made in code outside of your controllers. Therefore, you have to tell AngularJS that changes have been made. You do this by using the following command:

$rootScope.$apply();

Of course, you can also just use $scope.$apply();

Import modules
In order to separate code, you can choose to put different modules into separate .js-files, and then import them into your app. This is done by using the following code:

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

and then in the external module, you write:

angular.module('NameOfExternalModule',[])

Remember to use ng-href instead of href
If you want to use AngularJS markup inside your links, you have to use ng-href instead of href. The reason is that it takes time for AngularJS to parse all HTML, and this means that if the user is quick he/she can click the link before it is actually parsed. This would result in a 404-page.

So, instead of writing this:

<a href="http://www.someurl.com/{{hash}}"/>

you should write this:

<a ng-href="http://www.someurl.com/{{hash}}"/>

Remember to use ng-src instead of src
This is similar to using ng-href instead of href. Whenever you insert images and want to use Angular-markup, you have to use ng-src. Just like this:

    <ul id="preview" ng-repeat="image in images">
        <li><img ng-src="{{image}}" /></li>
    </ul>

Use ng-show to show/hide elements
If you want HTML elements to be either hidden or shown based on some value or some function, you can use ng-show. An example would be:

<li ng-show="$route.current.params.ordernumber != null">

In the example above, the list-element will either be hidden or shown if the expression evaluates to either true or false.

Execute functions onload
Sometimes, you want to execute functions immediately when the app is loading. You can do this by inserting code like the following before the controllers are being executed:

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

// Install database onload - config settings
app.run(function(globals, $rootScope) {
   // Your code here....

});

The app.run-function will execute immediately.

Execute global functions
Often you don’t want to execute functions immediately, but would like them to be available globally. You can do this by either storing functions in the rootScope or creating services. I normally prefer creating functions put in the rootScope because there’s less code to be written. :-)

Functions in rootScope:

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

    // Your function
    app.run(function($rootScope) {
      $rootScope.fill = function(globals, serviceorders) {
      	// Your code here....

  		}
  	});

  	// call your function
  	$scope.fill(globals, serviceorders);

Services:

    var app = angular.module('mySuperDuperApp', ['SomeExternalModule']);
      app.service('acceptOrderService', function() {
      this.acceptorder = function($scope, globals, primarykey, callback) {
      	// Your code here....

    	}
    });

    // call your service
    acceptOrderService.acceptorder($scope, globals, $routeParams.ordernumber, function(status) {
        $location.path("/orderdetails/" + $routeParams.ordernumber);
        $scope.$apply();
    });

Global variables
Just as you create global functions, you can also create global variables. This is done by using code like the following:

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

    // Define global variables
    app.value('globals', {
            db : null,
            databasename : "serviceapp",
            table1 : "serviceorders",
            table2 : "photos",
            keypath1 : "primarykey",
            keypath2 : "data",
            index1 : "needsynchronization",
            index2 : "ordernumber",
            index3 : "timestamp",
            json_file : "/js/MobileServiceApp/data.json",
            db_version : 1,
            globalcounter : 0,
            loaded : false,
            serviceorders : {},
            hasdata : false,
            image_max_width: 620,
            image_max_height: 620,
            image_quality: 0.7
        }
    );

Avoid AngularJS escaping your HTML
Normally, AngularJS will escape HTML displaying e.g.

this is <br>escaped

To avoid this, you can use the following code:

<p ng-bind-html-unsafe=(article.content.IntroText)></p>

Of course, this ng-bind-html-unsafe can be used on all HTML-tags and also in ng-repeat.

Feeding values from backend to Angularjs
Sometimes, you need to send data from a backend-system to Angularjs. I know that the prober way of handling this is to load the data via a REST-interface, but sometimes this is just not possible. As in my case where I often need values coming directly from a CMS-product.

Here, you can use the ng-init method. You can see how in the following snippet. Here, I load an itemid into my AngularJS application. One important thing, though, is that if you want to make absolutely sure that your values are available for the controllers, you need to insert the ng-init BEFORE your ng-controller. Otherwise, you can’t be sure that the value will be available.

<div  class="container-fluid" id="article" ng-init="articleid= <!--@Item.Id-->">
	<div id="row-holder" ng-controller="ArticleCtrl">

After you have inserted this snippet, you can then access your value like this:

function ArticleCtrl($scope, mediaservice, $filter) {
    // Load videos related to the article - default
    mediaservice.preload($scope.articleid, $scope);
}

Does a form contain new data?
I needed to implement a functionality that would warn the user about changed data in a form when the user tried to navigate away from the form. Luckily, in AngularJS this is very easy. You just use the $dirty variable. Just like so:

  $scope.back = function($event, ConfirmBackText) {
    $event.preventDefault();
    if ($scope.mainform.$dirty) {
      if (confirm(ConfirmBackText)) {
        window.history.back();
      }
    } else {
      window.history.back();
    }
  };

Notice, that I check whether or not $scope.mainform.$dirty is true or false. If it is true, I pop the confirm dialogue.

That’s it for now. I will keep updating this blogpost whenever I discover new “features”. I hope that you can use it.