1. Welcome to PHCorner Forums. Take a moment to Sign up and gain unlimited access and extra privileges that guests are not entitled to, such as:

    All that and more! Registration is quick, simple and absolutely free. Join our community today!

Angular.js example application

Discussion in 'Coding, Programming' started by Jeanh, Nov 20, 2015.



  1. Table Of Contents

    • Please or Register to view links
    • Please or Register to view links
    • Please or Register to view links
    • Please or Register to view links
    • Please or Register to view links
    • Please or Register to view links
      Please or Register to view links called Please or Register to view links, which is a JavaScript MVC framework, which is a bit of departure for me from my usual XAML influenced world. It is however good to try things out to get an appreciation of how you would do things in different languages/environments (my old mentor Fredrik Bornander (AKA the Swede) told me that), so I decided to take Please or Register to view links for a spin.

      This article will talk about some of the fundamental ideas behind Please or Register to view links, and shall then focus on the specifics of the demo application that I have created to go along with this article.

      Before we get into the actual article I will just breifly (not too techie just yet, though I know you lot will want to see that, and it will come don't worry) talk about what the demo app does in plain terms, so you know how to drive the thing yourself when you download it.



      [paste:font size="6"] Please or Register to view links web site. Anyway what the the publisher does it to allow user to click a image, when the user clicks an image a message is sent to the Please or Register to view links web site using web sockets (more on this later). In a nutshell that is all the publisher does.


      Website

      The Please or Register to view links web site, is where the fun stuff happens (at least in my opinion). The Please or Register to view links web site essentially carries out these tasks
      • When on the root page, the Please or Register to view links will listen to messages sent via the WPF publisher over a web socket, which then gets broadcast internally using the Please or Register to view links to anyone interested, which in this article is essentially just the root page.
      The root page will display an image tile for each allowable message received. The image tile may be resized and dragged around thanks to some Please or Register to view links love. The user may then choose to save the image tile to their favourites, which will cause ALL the information about the image tile to be saved to HTML 5 local storage. This information includes size, position etc etc, so when the user comes back to the root page, their favourites (the ones they saved) should appear exactly as they were before. The user may also decide to remove image tiles from their favourites from the root page.
      • The user may also choose to navigate to a favourites page that will display some thumbnail images of their HTML 5 local storage persisted favourites. These thumbnails may be clicked on to show a pretty standard ColorBox (Lightbox etc etc type thing) jQuery plugin.
      • The user may also choose to a view static about page, which I simply added to make enough routes to make things more worthwhile when demonstating the routing within Please or Register to view links
      So in plain terms that is all there is to it, this image may help to solidify what I just stated in words, picture says 1000nd words and all that:

      [​IMG]

      This is what the 2 parts of this articles demo code should look like when they are running correctly:

      Please or Register to view links

      Click image for larger version

      IMPORTANT NOTE:

      You should really ensure that you follow these steps to run the demo code successfully
      1. If you find that some of the Publisher.Wpf projects references can not be found, I have included them in a "Lib" folder where you can simply re-reference them from
      2. You should ensure that the Publisher.Wpf project is run up first and that it is displaying all the images. This can be done by building the Publisher.Wpf project to an EXE and simply finding the EXE in your file system and double clicking it to run (or use Visual Studio to run up an instance in DEBUG)
      3. You should then run the Angular web site, make sure that you have Index.html set as start page in Visual Studio and then use Visual Studio to run the Angular web site




      [paste:font size="6"] Please or Register to view links. This section will be a mixture of my own words, and text lifted directly from the Please or Register to view links web site. I will not be covering everything that Please or Register to view links does, as that would be more like a book really, and I just do not have that much time. I will however be covering some of the basic Please or Register to view links building blocks, so should you read this article and think "mmm...This Angular stuff intrigues me, where can I learn more", by following the the hyperlinks of course


      [paste:font size="5"] Please or Register to view links application will at some point need to use a Please or Register to view links ng-app binding within the html, or via some code that does the same job as the declaritive html binding. This code essentialy bootstraps Please or Register to view links and lets it know what context the application is running in. For example you may have the following code:

      Hide Copy Code
      <div id="outer">
      <div id="inner" ng-app="myApp">
      <p>{{name}}</p>
      </div>
      <p>This is not using the angular app, as it is not within the Angular apps scope</p>
      </div>

      It can be seen that we can tell a particular section of the html to act as an Please or Register to view links application. In this example what this means is that the div with id="inner" WILL have a section of the html (though there is nothing to stop you making the actual Body tag be the angular app) that is considered to be the Please or Register to view links application, and as such will have full access to the Please or Register to view links application features (which we will discuss below).

      Whilst the div with the id="outer" WILL NOT be considered to be part of the Please or Register to view links application, and as such WILL NOT have ANY access to the Please or Register to view links application features (which we will discuss below).

      [paste:font size="5"] Please or Register to view links are much the same as they are in WinForms/WPF or Silverlight. They are little helper classes that may provide functionality that could be used across the application. In strongly typed languages such as C# we would typically make these services implement a particular interface and inject them (via constructor or property injection) into our application code. We would then be able to provide fakes/mocks of these services within our tests, or provide alternative versions if the underlying system changes (for example swapping from Mongo DB to Raven DB storage)

      Whilst Please or Register to view links doesn't support interfaces (though you could use TypeScript for that) it does support the injection of real/fakes services into its code. In fact I would say that one of Please or Register to view links main points is it supports IOC out of the box.

      [paste:font size="5"] Please or Register to view links

      The recommended approach is to actually split up your modules, such that you may have a structure like this:
      • A services module
      • A directives module
      • A filters module
      • Application module(s)
      This is how you might define a Please or Register to view links module, the give away is the use of the function "module" that Please or Register to view links provides

      Hide Copy Code
      angular.module('xmpl.service', []).
      value('greeter', {
      salutation: 'Hello',
      localize: function(localization) {
      this.salutation = localization.salutation;
      },
      greet: function(name) {
      return this.salutation + ' ' + name + '!';
      }
      }).
      value('user', {
      load: function(name) {
      this.name = name;
      }
      });

      [paste:font size="5"] Please or Register to view links was built with dependency injection (IOC) in mind, as such a lot of the infrastructure may be swapped out for mocked versions, or controllers could be tested using mocked services. Showing how to do this, or how to test Please or Register to view links applications in not in the scope of this article. If you want to know that, visit the Please or Register to view links docs, or get a book. Sorry

      Following on from the previous module example, this is what a module might look like that took some dependencies, in this case a module that we just defined above, where we use the 2 values 'greeter' and 'user' which are both functions available within the 'xmpl.service' module. This module could be supplied with a mocked version of the 'xmpl.service' module.

      Hide Copy Code
      angular.module('xmpl', ['xmpl.service']).
      run(function(greeter, user) {
      // This is effectively part of the main method initialization code
      greeter.localize({
      salutation: 'Bonjour'
      });
      user.load('World');
      })



      [paste:font size="5"] Please or Register to view links is primarily a single page application framework, and as such has the concept of view templates that can be applied in response to a certain route being requested.

      Routing in Please or Register to view links is actually not that different to routing in things like ASP MVC or even Please or Register to view links for that matter.

      Routing is accomplished by using a prebuild service called Please or Register to view links, which comes for free as part of Please or Register to view links. It allows the user to configure their routes using a very simple API, which boils down to these 2 functions
      1. when(path, route)
        • Where the the route object has the following properties
          • controller
          • template
          • templateUrl
          • resolve
          • redirectTo
          • reloadOnSearch
      2. otherwise(params)
      Here is a little example

      Hide Copy Code
      $routeProvider
      .when('/products', {
      templateUrl: 'views/products.html',
      controller: 'ProductsCtrl'
      })
      .when('/about', {
      templateUrl: 'views/about.html'
      })
      .otherwise({ redirectTo: '/products' });;

      [paste:font size="5"] Please or Register to view links views will contain additional (non standard html) bindings that allow the view template to display data from a Please or Register to view links scope object. The scope object would typically come from a controller (though it is not limited to coming from a controller, it could be inherited, or be created via a directive say).

      Here is a small example of a view, notice the bindings used in there, such as the use of ng-model and ng-repeat and also the usage of some of Please or Register to view links pre-built fiters, nameley filter and orderBy (Please note I will not be covering filters in this article)

      Hide Copy Code
      Search: <input ng-model="query">
      Sort by:
      <select ng-model="orderProp">
      <option value="name">Alphabetical</option>
      <option value="age">Newest</option>
      </select>


      <ul class="phones">
      <li ng-repeat="phone in phones | filter:query | orderBy:eek:rderProp">
      {{phone.name}}
      <p>{{phone.snippet}}</p>
      </li>
      </ul>

      [paste:font size="5"] Please or Register to view links also has the idea of scope, and heirarchical scope, which is accessable in binding(s) within the html using various prebuilt key words.

      Please or Register to view links also supports nested/heirarchical scopes, which can get a bit confusing at times. I personally found that one of the best ways to work with Please or Register to view links and its scopes is to install the Please or Register to view links Chrome addin, which has a nice way of allowing youto drill into scopes using a scope inspector (kind of like Snoop for WPF or SilverlightSpy for Silverlight)

      This diagram may help solidify the concept of view-controller-scope.

      Please or Register to view links

      Click image for larger version

      [paste:font size="5"] Please or Register to view links makes use of a pretty novel concept, which are known as directives. Directives are clever chaps, that actually allow you to create extra attributes, or even new DOM fragments. This is all controlled by applying certain constraints to a directive, such that you may wish to state that a certain directive may only be used as an attribute, or that it can only be used as an element. You can sort of think of directives as custom controls.

      Directives also follow the normal Please or Register to view links rules, in that they support dependency injection, and they are also scope aware.

      One of the best bits of information I came across when writing this article was this one by Bernardo Castilho : Please or Register to view links, I urge you to read that, it is an excellent article, and by the end of it you will totally get directives.



      Anyway that concludes the Please or Register to view links basics, it's now on with the actual demo application code walkthrough.



      [paste:font size="6"] Please or Register to view links library to talk to the Please or Register to view links web site
    • That is has a Win8 type panorama, so you can scroll around using the mouse
      Here is what the WPF publisher should look like when it is running:

      [​IMG]

      Now on to the important part, the web socket code.

      Hide Shrink [​IMG] Copy Code
      public class WebSocketInvoker : IWebSocketInvoker
      {
      List<IWebSocketConnection> allSockets = new List<IWebSocketConnection>();
      WebSocketServer server = new WebSocketServer("ws://localhost:8181");

      public WebSocketInvoker()
      {
      FleckLog.Level = LogLevel.Debug;
      server.Start(socket =>
      {
      socket.OnOpen = () =>
      {
      Console.WriteLine("Open!");
      allSockets.Add(socket);
      };
      socket.OnClose = () =>
      {
      Console.WriteLine("Close!");
      allSockets.Remove(socket);
      };
      socket.OnMessage = Console.WriteLine;
      });
      }

      public void SendNewMessage(string jsonMessage)
      {
      foreach (var socket in allSockets)
      {
      socket.Send(jsonMessage);
      }
      }
      }

      This is all there is to that part actually, pretty cool huh (all thanks to the Please or Register to view links library). Now I realise that there may be some of you out there that are like why didn't you use Please or Register to view links, well I could have, but that would have been a pretty different article, for this one I wanted to concentrate purely on the web client side of things, so chose to use a raw web socket, and the Please or Register to view links library fits that bill perfectly.

      In this code the SendNewMessage will be called when a user clicks an image, and the name of the image clicked will be sent via the web socket to the Please or Register to view links web site. The Please or Register to view links web site, has a copy of all the possible images, as I did not want to get into complex POST operations of files, and obviously a web server can't show a local file (which would be a security risk in my (and any others) opinion), so I opted for shared files that the publisher and the Please or Register to view links web site both know about for the purpose of this demo application/article.





      [paste:font size="6"] Please or Register to view links web site. Hopefully if you have got to this point, some of the stuff I mentioned above will begin to make sense when you see some code.



      [paste:font size="5"] Please or Register to view links I was looking into using Please or Register to view links, which is a module loading framework for JavaScript, which allows you to specify your dependencies and preferred module load order. I wrote about this in another article, which you can read here: Please or Register to view links

      I have kind of built apon that a bit more in this article (with a little bit of a kick start with the source code that comes with the Please or Register to view links O'Reilly book : Please or Register to view links)

      So that is where you can do more reading around this subject if you want to, but let's ©râck on and see what the Please or Register to view links element of the attached Please or Register to view links website looks like shall we.

      It starts with this sort of code in the main Please or Register to view links page (Index.html):

      See index.html

      Hide Copy Code
      <html>
      .....

      <script data-main="scripts/main"
      src="scripts/vendor/require.js"></script>
      </html>

      This is standard Please or Register to view links code that tells Please or Register to view links which is the main bootstrap code file that it should run. It can be seen that this is scripts/main, so let's have a look at that now shall we

      See scripts\main.js

      Hide Shrink [​IMG] Copy Code
      // the app/scripts/main.js file, which defines our RequireJS config
      require.config({
      paths: {
      angular: 'vendor/angular.min',
      jqueryUI: 'vendor/jquery-ui',
      jqueryColorbox: 'vendor/jquery-colorbox',
      jquery: 'vendor/jquery',
      domReady: 'vendor/domReady',
      reactive: 'vendor/rx'
      },
      shim: {
      angular: {
      deps: ['jquery', 'jqueryUI', 'jqueryColorbox'],
      exports: 'angular'
      },
      jqueryUI: {
      deps: ['jquery']
      },
      jqueryColorbox: {
      deps: ['jquery']
      }
      }
      });

      require([
      'angular',
      'app',
      'domReady',
      'reactive',
      'services/liveUpdatesService',
      'services/imageService',
      'services/localStorageService',
      'controllers/rootController',
      'controllers/favsController',
      'directives/ngbkFocus',
      'directives/draggable',
      'directives/resizable',
      'directives/tooltip',
      'directives/colorbox'
      // Any individual controller, service, directive or filter file
      // that you add will need to be pulled in here.
      ],
      function (angular, app, domReady) {
      …….
      …….
      …….
      …….

      }
      );

      There is quite a bit going on here.It does however boil down to 3 parts
      1. We configure Please or Register to view links with the paths of where the JavaScript files are
      2. We configure a preferred load order for Please or Register to view links by using the Please or Register to view links shim. The shim essentially sets up the dependencies for the libraries to load
      3. We then use Please or Register to view links [Require] to tell the Please or Register to view links application what dependencies we would like to be satisfied.
      I have also used Please or Register to view links to satisfy the demo applications controller requirements. An example of which is as follows:

      Hide Copy Code
      define(['controllers/controllers',
      'services/imageService',
      'services/utilitiesService',
      'services/localStorageService'],
      function (controllers) {
      controllers.controller('FavsCtrl',
      ['$window',
      '$scope',
      'ImageService',
      'UtilitiesService',
      'LocalStorageService',
      function (
      $window,
      $scope,
      ImageService,
      UtilitiesService,
      LocalStorageService) {

      ......
      ......
      ......
      ......
      ......


      }]);
      });



      [paste:font size="5"] Please or Register to view linksconfiguration), lets just have a quick look at the actual Please or Register to view links bootstrapping bit.

      This is the bit that we discussed right at the start of this article, you know the bit that actual makes the attached code an " Please or Register to view links" application.

      That part is as follows:

      Hide Copy Code
      function (angular, app, domReady) {
      'use strict';
      app.config(['$routeProvider',
      function ($routeProvider) {
      ....
      ....
      ....
      }]);
      domReady(function () {
      angular.bootstrap(document, ['MyApp']);

      // The following is required if you want AngularJS Scenario tests to work
      $('html').addClass('ng-app: MyApp');
      });
      }

      This bootstrapping does 2 things:
      1. It sets up the available valid routes, which we will be looking at next
      2. It relies on a special Please or Register to view links addin called "DomReady" which works much the same way as Please or Register to view linksand its ready() event. After the dom is ready the "HTML" element is attributed up to make it act as the Please or Register to view links application.
      There is also the question of where the "MyApp" module comes from. Who creates that prior to it being bootstrapped here?

      The answer to that is that is lives in its own file "app.js" which looks like this

      See scripts\app.js

      Hide Copy Code
      // The app/scripts/app.js file, which defines our AngularJS app
      define(['angular', 'controllers/controllers', 'services/services', 'filters/filters', 'directives/directives'], function (angular) {
      return angular.module('MyApp', ['controllers', 'services', 'filters', 'directives']);
      });





      [paste:font size="5"] Please or Register to view links $routeProvider service, where all the set up code is done within the boostrapping file main.js.

      Hide Copy Code
      unction ($routeProvider) {
      $routeProvider
      .when('/', {
      templateUrl: 'views/root.html',
      controller: 'RootCtrl'
      })
      .when('/favs', {
      templateUrl: 'views/favs.html',
      controller: 'FavsCtrl'
      })
      .when('/about', {
      templateUrl: 'views/about.html'
      }).otherwise({ redirectTo: '/' });;
      }

      I think it is pretty obvious from that there are 3 routes as stated above and how they are configured. So I wil not say any more on that.





      [paste:font size="5"] Please or Register to view links Draggable/Resizable UI element, providing it has not already got an element with the same image name shown.

      It also allows the user to save the images to HTML 5 local storage and to remove them from local storage too. If there are items aready within local storage theirs details are used as the initial start state for the Root page. I have to say this looks cool, as all the information is persisted, so it remembers the sizes, locations, ZIndex, so it comes back exactly how you saved it.

      So in general terms that is what the root page does.

      This is what the root page looks like

      Please or Register to view links

      Click image for larger version

      So that is what it looks like, want to see some code?



      [paste:font size="4"] Please or Register to view links. To achieve dynamic CSS that is updated when objects change you need to provide a function to call, which is what you can see here in thestyleProps() function:

      Hide Copy Code
      self.styleProps = function () {
      return {
      left: self.left + 'px',
      top: self.top + 'px',
      width: self.width + 'px',
      height: self.height + 'px',
      position: 'absolute'
      };
      };

      The markup that uses this as follows, which means when ever an update is done on the JSON object the CSS is updated too, and the html reflects this. This was not that easy to find out, so make sure you read this bit a few times, solidify that knowledge, wedge it in there good and hard

      Hide Copy Code
      ng-style="imageitems[$index].styleProps()"

      [paste:font size="4"] Please or Register to view links library, but when working with Please or Register to view links, there is a definate Please or Register to view links way, and littering your controller code with DOM changing jQuery code is most certainly NOT the Please or Register to view links way. So what option does that leave us. Well that is really where Please or Register to view links directives fit, they are designed to replace and enhance the DOM, that is what directives do best.

      So anytime you need to alter the DOM directly (not through scope changes) you should be thinking about using Please or Register to view links directives.

      So all that said, it turs out to be a simple matter to create a small Please or Register to view links Please or Register to view links directive, which can be seen in the <table....draggable resizable>....</table> table tags in the markup.

      Here is the draggable directives code, where you can see that this is restricted to attribute usage, and simply delegates the work to the actual Please or Register to view links (which was referenced as a requirement within the Please or Register to view links configuration, so we know its loaded ok, otherwise the Please or Register to view links would not have started, as it has Please or Register to view links as a dependency within the Please or Register to view links configurtion).

      One thing worth mentioning here is that once the drag has finished I wanted to inform the controller of the new position values, such that they could be reflected in the styling. Now since the positioning is done outside of an Please or Register to view links controllers scope (as it is done via the inbuilt Please or Register to view links code), we need to get thedraggable directive to update the controller scope, such that it knows something outside of it has changed one of its variables. Luckily the Please or Register to view links draggable widget provides a nice callback that we can make use of, which we use and then tell the Please or Register to view links controller's scope that something has changed, this is done using the Please or Register to view links $scope.apply() which is used for this purpose

      Hide Copy Code
      define(['directives/directives'], function (directives) {
      directives.directive('draggable', ['$rootScope', function ($rootScope) {
      return {
      restrict: 'A',
      //may need the model to be passed in here so we can apply changes to its left/top positions
      link: function (scope, element, attrs) {

      element.draggable(
      {
      stop: function (event, ui) {
      scope.$apply(function() {
      scope.updatePosition(
      scope.imageitem.name,
      {
      left: ui.position.left,
      top: ui.position.top
      }
      );
      });
      }
      });
      }
      };
      }]);
      });

      Where this is the controller code that gets called when the directive calls into the controller's scope

      Hide Copy Code
      // NOTE: $scope.$apply is called by the draggable directive
      $scope.updatePosition = function (name, pos) {
      var idx = $scope.imageitems.propertyBasedIndexOf('name', name);
      var foundItem = $scope.imageitems[idx];
      foundItem.left = pos.left;
      foundItem.top = pos.top;
      };

      [paste:font size="4"] Please or Register to view links based Please or Register to view links directive. Here is its code:

      Hide Shrink [​IMG] Copy Code
      define(['directives/directives'], function (directives) {
      directives.directive('resizable', ['$rootScope', function ($rootScope) {
      return {
      restrict: 'A',
      //may need the model to be passed in here so we can apply changes to its left/top positions
      link: function (scope, element, attrs) {

      element.resizable(
      {
      maxHeight: 200,
      minHeight: 100,
      //aspectRatio: 16 / 9,
      stop: function (event, ui) {
      scope.$apply(function () {
      scope.updateScale(
      scope.imageitem.name,
      {
      top: ui.position.top,
      left: ui.position.left
      },
      {
      width: ui.size.width,
      height: ui.size.height
      }
      );
      });
      }
      });
      }
      };
      }]);
      });

      As before since we are changing things (the scale of a UI element) outside the knowledge of the Please or Register to view linkscontroller, we need to get the directive to update the controller scope, here is the relevant code:

      Hide Copy Code
      // NOTE: $scope.$apply is called by the resizable directive
      $scope.updateScale = function (name, pos, size) {
      var idx = $scope.imageitems.propertyBasedIndexOf('name', name);
      var foundItem = $scope.imageitems[idx];
      foundItem.left = pos.left;
      foundItem.top = pos.top;
      foundItem.width = size.width;
      foundItem.height = size.height;
      };

      [paste:font size="4"] Please or Register to view links

      Click image for larger version

      And this is what it would look like when you have clicked one of the thumnbails

      Please or Register to view links

      So how does this page work. Well one of the hard parts was something that you would all probably think was a very trivial thing to do. So in local storage we have a 1 dimensional JSON stringified array, and I wanted to turn that into a 2 dimensional table layout that I could use with Please or Register to view links ng-repeat binding.

      [paste:font size="4"] Please or Register to view links $window rather than "window" to allow the $window service to be replaced by a mock
    • We make use of the LocalStorageService that we saw earlier
    • We make use of the UtilitiesService that we saw earlier
    • We make use of the ImageService that we saw earlier


    [paste:font size="4"] Please or Register to view links once you have the correct structure in your scope to iterate over



    [paste:font size="4"] Please or Register to view links configuration, so we know its loaded ok, otherwise the Please or Register to view links would not have started)

    Hide Copy Code
    define(['directives/directives'], function (directives) {
    directives.directive('colorbox', ['$rootScope', function ($rootScope) {
    return {
    restrict: 'A',
    //may need the model to be passed in here so we can apply changes to its left/top positions
    link: function (scope, element, attrs) {
    $(element).colorbox({ rel: 'group3', transition: "elastic", width: "50%", height: "50%" });
    }
    };
    }]);
    });



    [paste:font size="5"] Please or Register to view links

    Click image for larger version





    [paste:font size="6"] Please or Register to view links

    [paste:font size="5"]Share
     
Tags / Keywords: