AngularJS UI-Router Nested Views

For more articles on AngularJS, refer to these links.

Introduction

Nested Views have parent child relationship. This means, their properties are available for use for all the child states. There are several methods for creating nested states.

One way to create a nested state is by using dot notation and the other is by using parent property with the parent name as string.

In our Students page, there is a list of students. So, we want the count of how many students are there and the count for the number of males and females. The first step is to create a C# class which will act as container for these.

as container  
 
Name it as studentTotals.cs

Assign the get set properties for total, males, and females.
 
The next step is to create a web service method in our existing web service, that returns total students. So, we will write the following code.
  1. [WebMethod]  
  2. public void GetStudentTotals()  
  3. {  
  4. // System.Threading.Thread.Sleep(2000);  
  5. studentTotals totals = new studentTotals();  
  6. // List<Student> listStudents = new List<Student>();  
  7. string cs = ConfigurationManager.ConnectionStrings["Test"].ConnectionString;  
  8. using (SqlConnection con = new SqlConnection(cs))  
  9. {  
  10. string query = "SELECT COALESCE(Gender,'GrandTotal') AS Gender,COUNT(*) AS Total from students GROUPBY ROLLUP(Gender)";  
  11. SqlCommand cmd = new SqlCommand(query, con);  
  12. con.Open();  
  13. SqlDataReader sdr = cmd.ExecuteReader();  
  14. while (sdr.Read())  
  15. {  
  16. switch (sdr["Gender"].ToString())  
  17. {  
  18. case "Male":  
  19. totals.males = Convert.ToInt32(sdr["Total"]); break;  
  20. case "Females":  
  21. totals.females = Convert.ToInt32(sdr["Total"]); break;  
  22. default:  
  23. totals.total = Convert.ToInt32(sdr["Total"]); break;  
  24. }  
  25. }  
  26. }  
  27.   
  28. JavaScriptSerializer js = new JavaScriptSerializer();  
  29. Context.Response.Write(js.Serialize(totals));  
  30. }   
The name of our method is GetStudentTotals and we are creating the instance of the class which we had created before as studentTotals.cs with three properties - totals, males and females respectively.

And then, we are reading the connection string as Test. From our web.config file, we are using SQL connection object using the query -
 
SqlConnection con = new SqlConnection

Now, we are building our SQL command object. For that, we have written a SQL query. Let's check the query in SQL and see the output.

query  

We have got two columns - gender and total. Within gender column, we have male and grand total, and within the total column, we have respective count.

So, we are building our SQL command by using sqlcommand object.

SqlCommand cmd = new SqlCommand(query, con);

Then, we are opening the connection and executing the command.

con.Open();
SqlDataReader sdr = cmd.ExecuteReader();


And then, we are looping through the rows that are returned. Notice that in our SQL query, we didn’t get any value for female but in code, I have already written a condition for male. So, we will comment that condition for female.

Now, let’s check our web service.

web service  

Click on students total and,

students total  
 
Click on Invoke and we will see JSON output displaying total,

students total  
The next step is to create student Parent state which will be the parent for students and students details state.

So in our script.js file we modify our controller as,
  1. .state("studentParent", {  
  2.     url: "/students/",  
  3.     controller: "studentParentController",  
  4.     controllerAs: "stdParentCtrl",  
  5.     templateUrl: "Templates/stduentParent.html",  
  6.     resolve: {  
  7.         studentTotals: function($http) {  
  8.             return $http.get("StudentService.asmx/GetStudentTotals")  
  9.                 .then(function(response) {  
  10.                     return response.data;  
  11.   
  12.                 })  
  13.         }  
  14.   
  15.     },  
  16.     abstract: true  
  17. })  
In this we had a student Parent and the URL respective to the controller name as student Parent Controller and we are using controller as syntax here and we are using resolve property here to retrieve the web method which we had done in our web service.

We had included abstract: true we are making this state as an abstract so what’s an abstract state?

An abstract state is similar to abstract class in C#. Can we create an instance of abstract class? We can’t. Similarly, the abstract state cannot be activated by itself it is implicitly activated when a child statement is activated.

The benefit of using abstract state is that the URL of the parent state is prepended to the URL of the child state. That means we can remove any redundant part from the child state to the URLs. we will see in controller how to remove that.

Another benefit of the parent state is that whatever data we have exposed with the help of resolve property, that data will be available for all the child states.

The next step is to make student details and students to be the child states.

We will use dot to represent parent and child states as-

state("student Parent.students", {

.state("student Parent.student Details", {


Rest of the code is same.

Now if you look at the URL of parent and child state both are the same. So in the child state there is no point in having redundant part so remove URL of child state and replace by “/” as,
redundant part  

Now the next step is we need to create student parent controller function.

redundant part
So we had created studentparent controller in that we had passed studenttotals as parameter as we are using resolve property in the controller.

The next step is to create a student parent template and bind it with binding expression.

Add a HTML page and name it as stduentParent.html now in that write this code

controller.

We are using controller as syntax we had bound the respective properties with binding expression.

We are also going to include ui-view directive just below that so that our partial template studentparent.html will get loaded in ui-view directive.
  1. <h1>Total Male Students: {{stdParentCtrl.males}}</h1>  
  2. <h1>Total Female Students : {{stdParentCtrl.females}}</h1>  
  3. <h1>Total : {{stdParentCtrl.total}}</h1>  
  4. <ui-view></ui-view>  
Now in our index.html we have to modify students link:

modify students
 
We had included the parent state here. Now similarly modify other links for students.html and studentdetails.html as

Students.html

modify students

Studentdetails.html

Studentdetails.html 
Now save the changes and build the app,

At the moment you are in root page,

root page
 
Click on students,

students  

As you can see we got the count total number of males and females and total number of students from our web service.

When you click on any student you will see,

students

The same output displaying the count.

The parent state resolved dependencies are available to child states. To prove this, we want to display total number of students on the student’s page against list of student’s text.

We will inject studentTotals in the studentscontroller function as,

the studentscontroller function

So our script.js code is,
  1. /// <reference path="angular.min.js" />  
  2. /// <reference path="angular-route.min.js" />  
  3. var app = angular.module("Demo", ["ui.router"])  
  4.     .config(function($stateProvider, $urlMatcherFactoryProvider, $urlRouterProvider, $locationProvider) {  
  5.         // $routeProvider.caseInsensitiveMatch = true;  
  6.         $urlRouterProvider.otherwise("/home");  
  7.         $urlMatcherFactoryProvider.caseInsensitive(true);  
  8.         $stateProvider  
  9.             .state("home", {  
  10.                 url: "/home",  
  11.                 templateUrl: "Templates/home.html",  
  12.                 controller: "homeController as homeCtrl",  
  13.                 data: {  
  14.                     customData1: "Home State Custom Data 1",  
  15.                     customData2: "Home Statae custom Data 2"  
  16.   
  17.                 }  
  18.             })  
  19.   
  20.         .state("courses", {  
  21.             url: "/courses",  
  22.             templateUrl: "Templates/courses.html",  
  23.             controller: "coursesController as coursesCtrl",  
  24.             data: {  
  25.                 customData1: "Courses State Custom Data 1",  
  26.                 customData2: "Courses Statae custom Data 2"  
  27.   
  28.             }  
  29.   
  30.         })  
  31.   
  32.         .state("studentParent", {  
  33.             url: "/students/",  
  34.             controller: "studentParentController",  
  35.             controllerAs: "stdParentCtrl",  
  36.             templateUrl: "Templates/stduentParent.html",  
  37.             resolve: {  
  38.                 studentTotals: function($http) {  
  39.                     return $http.get("StudentService.asmx/GetStudentTotals")  
  40.                         .then(function(response) {  
  41.                             return response.data;  
  42.   
  43.                         })  
  44.                 }  
  45.   
  46.             },  
  47.             abstract: true  
  48.         })  
  49.   
  50.   
  51.   
  52.   
  53.         .state("studentParent.students", {  
  54.             url: "/",  
  55.             templateUrl: "Templates/students.html",  
  56.             controller: "studentsController as studentsCtrl",  
  57.             resolve: {  
  58.                 studentsList: function($http) {  
  59.                     return $http.get("StudentService.asmx/GetAllStudents")  
  60.                         .then(function(response) {  
  61.                             return response.data;  
  62.                         })  
  63.                 }  
  64.   
  65.             }  
  66.         })  
  67.   
  68.         .state("studentParent.studentDetails", {  
  69.             url: "/:id",  
  70.             templateUrl: "Templates/studentDetail.html",  
  71.             controller: "studentdetailcontroller as StudentDetailCtrl"  
  72.         })  
  73.   
  74.         // .when("/studentssearch/:name?", {  
  75.         // templateurl: "templates/studentssearch.html",  
  76.         // controller: "studentssearchcontroller as studentssearchctrl"  
  77.         // })  
  78.         // .otherwise({  
  79.         // redirectto: "/home"  
  80.         // })  
  81.         $locationProvider.html5Mode(true);  
  82.     })  
  83.   
  84. .controller("homeController", function($state) {  
  85.     this.message = "Home Page";  
  86.     this.homeCustomData1 = $state.current.data.customData1;  
  87.     this.homeCustomData2 = $state.current.data.customData2;  
  88.     this.coursesCustomData1 = $state.get("courses").data.customData1;  
  89.     this.coursesCustomData2 = $state.get("courses").data.customData2;  
  90. })  
  91.   
  92. .controller("coursesController", function() {  
  93.     this.courses = ["c#", "SQL", "Oracle"];  
  94. })  
  95.   
  96. .controller("studentsController", function(studentsList, $state, $location, studentTotals) {  
  97.     var vm = this;  
  98.     vm.searchStudents = function() {  
  99.         if (vm.name) {  
  100.             $location.url("/studentsSearch/" + vm.name);  
  101.         } else {  
  102.             $location.url("/studentsSearch/");  
  103.         }  
  104.   
  105.     }  
  106.   
  107.   
  108.     vm.reloadData = function() {  
  109.         $state.reload();  
  110.     }  
  111.     vm.students = studentsList;  
  112.     vm.studentTotals = studentTotals;  
  113.   
  114. })  
  115.   
  116. .controller("studentParentController", function(studentTotals) {  
  117.   
  118.     this.males = studentTotals.males;  
  119.     this.females = studentTotals.females;  
  120.     this.total = studentTotals.total;  
  121.   
  122.   
  123. })  
  124.   
  125. .controller("studentdetailcontroller", function($http, $stateParams) {  
  126.     var vm = this;  
  127.     $http({  
  128.             url: "StudentService.asmx/GetStudents",  
  129.             method: "get",  
  130.             params: {  
  131.                 id: $stateParams.id  
  132.             }  
  133.         })  
  134.         .then(function(response) {  
  135.             vm.student = response.data;  
  136.         })  
  137. })  
  138.   
  139. //.controller("studentsSearchController", function ($http, $routeParams) {  
  140. // var vm = this;  
  141.   
  142. // if ($routeParams.name) {  
  143. // $http({  
  144. // url: "StudentService.asmx/GetStudentsByName",  
  145. // params: { name: $routeParams.name },  
  146.   
  147. // })  
  148. //.then(function (response) {  
  149. // vm.students = response.data;  
  150. //})  
  151. // }  
  152. // else {  
  153. // $http.get("StudentService.asmx/GetAllStudents")  
  154. // .then(function (response) {  
  155. // vm.students = response.data;  
  156. // })  
  157. // }  
Now in our student .html page we need to retrieve it from view model,

<h1>List Of Students({{studentsCtrl.studentTotals.total}}) </h1>

view model

Save the changes and reload the Page,

reload the Page

We had got list of students as expected.

Now click on the any student details page we will get this output as below,

output

Conclusion - So this was all about UI-Router nested views in AngularJS. Hope this article was helpful.