Introduction to Basics of Angular 1.4 newRouter - CodeProject

:

Introduction

This article introduces the basics of newRouter in AngularJS 1.4. This router developed by Angular Team will be an alternative to very popular open-source ui-router. 

Pre-requisites

In order to start using the newRouter, let us create a web page, download and include required JavaScript libraries.

  • Create a simple webpage using IDE of you choice
  • Create a folder named js, download and save the following JavaScript files in it
  • angular-1.4.0-beta.6.js library from Google CDN at AngularJ-1.4.0-beta.6
  • router.es5.js available at router.es5.js
  • Add regular AngularJS stuff, by creating an ng-app, module, controller.

After following the above mentioned steps, the Web page should look like:

<!DOCTYPE html>
<html ng-app="AngularApp">
    <head>
        <title>Start Page</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="js/angular-1.4.0-beta.6.js"></script>
        <script src="js/router.es5.js"></script>
    </head>
    <body ng-controller="MainController">
        
        <h1>{{title}}</h1>
        <div ng-viewport></div>
        
        
        <script>
            var module = angular.module("AngularApp", ["ngNewRouter"]);
            module.controller("MainController", function($scope, $router){
                $scope.title = "This is demo of application";
                
            });
        </script>   
     </body>
</html>

Please note that we have injected a new module ngNewRouter. The MainController too has a $router injected in it. This setups the basic infrastructure to use the newRouter

Creating placeholder : ngViewPort Directive

In order to inject an HTML template known as component into a web page, we need to create a container/placeholder div for it. This div should have an Angular directive ng-viewport. This ng-viewport is like ng-view/ui-view from Angular UI router and AngularJS core functionality.

......
<body ng-controller="MainController">
    <div ng-viewport></div>
</body>

Multiple ViewPorts/Named Viewports

We can create multiple placeholders/viewports by giving them name. This enables us to load multiple sections of screen using different HTML templates. This was not possible using AngularJS default routing framework(ngRoute).

......
<body ng-controller="MainController">
    <div ng-viewport="FirstViewPort"></div>
    <div ng-viewport="SecondViewPort"></div>
</body>

We will focus more on this later when we will talk about mappings of URL to components.

Define Component

In AngularJS 1.4, HTML templates are categorized as components. Each component is a reusable set having its own name, module, controller and HTML. AngularJS 1.4, by default requires a directory with name components be created. Inside this directory we need to create a folder per component name. Each component directory will have

  • Its own JavaScrpt file, containing its module definition, controller and other even handlers relevant
  • HTML file named as "componentname".html
  • Any other JavaScript files as and when required

For example, if we create a component named "navigation" we need to create a folder with the name navigation inside the components folder. AngularJS will take care, of glueing the container and component using $router, which we will see in a moment. The final directory structure will look like this.

 

Defining Component Module

As mentioned above, each component will have its own Angular module. The important thing to note is that there are certain rules, that are to be followed in order to make things work.

  • The name of the controller for the component should be "CapitalizedComponentName"Controller i.e. NavigationController for component named "navigation"
  • For modularity, each component module should be defined in its own js file under components directory, i.e. navigation.js for component named "navigation"
  • The component module needs to be added in the Mainmodule of the application, as dependency

 

A sample module navigation defined in navigation.js will look something like:

angular.module("AngularApp.navigation", []).controller("NavigationController", NavigationController);

// Note that the controller name is NavigationController. If we give something else, Angular wont be able to do binding.
function NavigationController() {
    // Controller bindings
}   

Defining Component Controller

Each component will have its own Controller identified by "CapitalizedComponentName"Controller. There is one special thing to note is that this controller is a bit different from other controllers. It doesn't need $scope for two-way binding. Angular wont be able to instantiate the controller if we pass $scope in it. It defines its observable properties on this.

A sample controller will look like:

// Note that the controller doesnt inject $scope.
function NavigationController() { // Dont inject $scope. Angular wont be able to create controller.
    this.title = "Welcome"; // Binding on this not $scope
    this.options = ["Home", "My Account", "Log Out"]; // Binding on this not $scope
}

Now, we must be thinking how will the two-way work as we are not adding variables on $scope. We will cover this in next section.

Defining Component View

A Component view is an HTML fragement that will be loaded in the DOM inside the ngViewPort div its bound to. Though this follows all the existing angular principles for views, there are some exceptions to it:

  • We don't need to add ng-controller to the HTML template. This will be taken care of by newRouter automatically.
  • When doing two-way binding we need to prefix the name of component before the variable

 

The following is a simple navigation.html. Please note that it doesnt have any ng-controller and component name "navigation" is prefixed to observable attributes.

<div>{{navigation.title}}</div>
<div>
    <ul>
    <li ng-repeat="opt in navigation.options">{{opt}}</li>
    </ul>
</div>

Consuming Components

Once the component is ready with its Module, Controller and View, its ready to be consumed in main view. This is where$router comes handy. $router provides capability to map a URL to a component. This means that when a particular URL is hit, the component is loaded in the ngViewPort div. Let see a few examples

Mapping with a single ViewPort

This example demonstrates that if there is a single viewport/placeholder then the HTML component is loaded into it automatically, when the matching URL is hit.

This is how the container HTML looks like

<body ng-controller="MainController">
    <h1>{{title}}</h1>
    <div ng-viewport></div>
 </body>

Router Mapping looks like

var module = angular.module("AngularApp", ["ngNewRouter", "AngularApp.navigation"]);
        module.controller("MainController", function($scope, $router){
            $scope.title = "This is demo of application";
            $router.config([
                {path:"/", component:  "navigation"}, // Maps the default path to navigation component.
                {path:"/profile", component:  "profile"} // Maps /profile to navigation component.

            ]);
});

$router.config(....) creates a mapping between the relative path and component. When the Angular sees that URL / (default in this case) is loaded, it loads the navigation.html in DOM along with its controller and appends it to the ngViewPort div. For "/profile" URL, component profile is loaded into the viewport div.

Mapping Multiple ngViewPorts

In the beginning of this article, we mentioned that we can create multiple viewports by giving them name. This allows for multiple parts of screen to be rendered using different HTML templates. This addresses the use cases where we need to load multiple sections of screen on a given URL. The following sample explains this kind of mapping:

Container HTML file looks like

 ....
<body ng-controller="MainController">
     <h1>{{title}}</h1>
     <div ng-viewport="FirstViewPort"></div>
     <div ng-viewport="SecondViewPort"></div>
</body>

Router Mapping looks like

var module = angular.module("AngularApp", ["ngNewRouter", "AngularApp.navigation"]);
        module.controller("MainController", function($scope, $router){
            $scope.title = "This is demo of application";
            $router.config([
                {path:"/", component:  {FirstViewPort: "navigation", SecondViewPort: "profile"}},
                {path:"/admin", component:  {FirstViewPort: "navigation", SecondViewPort: "admin"}},
        ]);
});

The above sample

  • For URL "/" maps the navigation component in placeholder FirstViewPort and profile component in SecondViewPort
  • For URL "/admin" maps the navigation component in placeholder FirstViewPort and profile "admin" in SecondViewPort

 

Redirecting to Components

In addition to mapping, the newRouter also supports routing the URL to components. What this means is that we can redirect the default URL "/" to a component "navigation" thus making is default screen component.

The following snippet explains the same:

var module = angular.module("AngularApp", ["ngNewRouter", "AngularApp.navigation", "AngularApp.profile", "AngularApp.admin"]);
module.controller("MainController", function($scope, $router){
    $scope.title = "This is demo of application";
    $router.config([
        {path:"/", redirectTo: "navigation"}, // The default URL is redirected to navigation component.
        {path:"/navigation", component: "navigation"},
        {path:"/admin", component: "admin"}
    ]);
});

Linking the Components - ngLink

Now we are done with creating placeholders/viewports, setting up components (HTML, controller), mapping the components with the URLs for single viewport and multiple viewports. Now we need to write that code that invokes this mapping on user action, like click of a link etc.

Angular provides a special directive ngLink that uses the <a> href attribute to invoke URL lookup such that the HTML components can be loaded. What this essentially means is that when applied on <a>, As per AngularJS documentation ngLink automatically generates a href attribute that looks up the $router mappings and loads the screen partials in the placeholders/viewports defined.

The following example displays how the router mapping looks like

var module = angular.module("AngularApp", ["ngNewRouter", "AngularApp.navigation", "AngularApp.profile", "AngularApp.admin"]);
module.controller("MainController", function($scope, $router){
    $scope.title = "This is demo of application";
    $router.config([
        {path:"/", redirectTo: "navigation"},
        {path:"/navigation", component: "navigation"},
        {path:"/admin", component: "admin"}
    ]);
});

The following example shows usage of ngLink

<body ng-controller="MainController">
    <h1>{{title}}</h1>
    <a ng-link="navigation()">Home</a>
    <a ng-link="admin()">Admin</a>
    <div ng-viewport></div>
</body>

Known Issues

As this is work in progress, there are couple of  issues that I encountered

1. Nested routing is not working

2. ngLink is not working for named viewports

Summary

In this article I have tried to explain the usage of newNgRouter which is part of Anguar 1.4 which is yet to be released. I found out that for the named viewports, I was not able to make ngLink work. I will update the document once I find a solution.

 

References