How to lazy load scripts in AngularJS app?

Some time ago I was having a problem with JS loading from partials in an AngularJS app and today I decided to share my solution with you. The problem is that when web page is loaded, javascript libs in partials are not being loaded. You have to force them to load. You can do this with some imagination, a directive and some jQuery (the last one is optional). 🙂

app.directive('script', [function() {
  function load_script(src, identifier) {		
    var s = document.createElement('script');
    s.type = 'text/javascript';
		
    if(identifier !== undefined) {
      // if element with some identifier exists, remove it                         
      jQuery('script[data-identifier="' + identifier + '"]').remove(); 
      // add an identifier to the new script object so we can remove it if needed
      s.setAttribute('data-identifier', identifier);
    }	
		     
    if(src !== undefined) {		
      s.src = src;
    }
    else {
      var code = elem.text();
      s.text = code;
    }

    document.body.appendChild(s);
  }

  return {
    restrict: 'E',
    scope: false,
    link: function(scope, elem, attr) {
      if (attr.type=='text/javascript-lazy') {
        load_script(elem.attr('src'), elem.attr('data-identifier'));
        elem.remove();
      }
    }
  };
}]);

When partial is loaded the directive happens for each sript object in that partial. If script is of type text/javascript-lazy, load_script function is called. This function creates new element of type text/javascript and adds src or code of lazy element to the new element. All elements with specific (custom) identifier are then removed from DOM and new element is appended to body. When appended to body, script gets forcefully loaded.

How to do detect when ngRepeat is finished and after that do something?

Today I am going to show you a simple solution of the problem, presented in the title of this post. You can detect end of ngRepeat with a directive:

app.directive('onLastRepeat', function() {
  return function(scope, element, attrs) {
    if(scope.$last)
      setTimeout(function() {
        scope.$emit('onRepeatLast');
      }, 0);
  }
});

As you can see I firstly check if last item is being processed. If this is true, I set timeout for 0ms. This is an ugly hack, but it works. The problem is that AngularJS has a process named $digest which asynchronously inserts data into template using ngRepeat directive and we don’t know when the process of data insertion is finished. After that we call our function inside of parent scope with $emit and there the magic happens 🙂 (in my case I reseted some widths and heights).

This solution only works if you have one ngRepeat inside of a controller. In other cases you could decide which function to call based on special attributes or something.

Have a nice day! 😉

How to detect device type with JavaScript?

There is a simple way how to detect if user is accessing your web site using mobile device (like phone, tablet or their hybrid brother phablet) or stationary device. You can detect it using navigator object. We will neglect small players on the mobile market and focus only on the main players. Example:

var mobile = {
  Android: function() {
    return navigator.userAgent.match(/Android/i);
  },
  BlackBerry: function() {
    return navigator.userAgent.match(/BlackBerry/i);
  },
  iOS: function() {
    return navigator.userAgent.match(/iPhone|iPad|iPod/i);
  },
  Opera: function() {
    return navigator.userAgent.match(/Opera Mini/i);
  },
  Windows: function() {
    return navigator.userAgent.match(/IEMobile/i);
  },
  any: function() {
    return (mobile.Android() || mobile.BlackBerry() || mobile.iOS() || mobile.Opera() || mobile.Windows());
  }
};

If you are interested if user is accessing your site using BlackBerry (obscure example, I know – why would anyone care about BlackBerry 🙂 ) you can do it like this:

var isandroid = function() {
  mobileArray = mobile.BlackBerry();
  if(typeof mobileArray !== 'undefined' && mobileArray != null && mobileArray.length > 0) {
    return true;
  }
  return false;
}

Or shortly:

var isandroid = function() {
  mobileArray = mobile.BlackBerry();
  return (typeof mobileArray !== 'undefined' && mobileArray != null && mobileArray.length > 0);
}

Push C# (Razor) objects to JavaScript array of objects

If you wonder, how to push C# objects to a JavaScript array in ASP.NET View using Razor, you came to the right place. 🙂 In this post I will tell you, how I achieved it.

Lets say I have a collection List<Category>. Object Category consists of parameters id, category name and list of subcategories (also of type Category):

public class Category {
  public int Id { get; set; }

  public string Name { get; set; }

  public List<Category> Subcategories { get; set; }
}

List<Category> is also my strongly typed View’s Model. My goal is to achieve Javascript array like this: [{“id”:…, “name”:”…”, “subcategories”:[{“id”:…, “name”:”…”}]},{…}]. In order to create objects like that, I must firstly create collection of anonymous objects and serialize it to string.

@{
  //serializer initialization
  System.Web.Script.JavascriptSerializer js = new System.Web.Script.JavascriptSerializer();

  var data = Model.Select(c => new {
    id = c.Id,
    name = c.Name,
      subcategories = c.Subcategories.Select(cc => new {
      id = cc.Id,
      name = cc.Name
    })
  });

  //serialize collection of anonymous objects
  string arrStr = js.Serialize(data);
}

Now I have to inject this string to JavaScript:

var categories = JSON.parse('@arrStr'.replace(/&quot;/g, '"'));

As you can see, I injected string using Razor. I also used replace function in order to replace all &quot; occurances with " (double quotes).

Warning: JavaScript replace function replaces only first occurrance of searched pattern. In order to replace all occurrances I had to use regular expression with flag g, which is used to perform a global match.

The last thing to do is to call JSON.parse, which converts properly processed string into JavaScript object. Now we have JavaScript array of desired objects, which can be used further in our JavaScript functions/events.