2

Friends..

For my understanding of how routing works in Angular I have created a simple application. This application has only two pages: 1. The first page will display all rows of the employee table. Upon clicking on a particular row, second page will display a form with details of that employee.

The list that is displayed on the first page uses the following code:

<table>
   <tr ng-repeat="employee in employees">
       <td>{{employee.firstname}} - {{employee. address}}</td>           
       <td><span ng-click="getSingleEmployeeDetails(employee.id)">Edit</a></td>
   </tr>
</table>

I am using the same controller for both these pages and this controller looks like below:

function EmployeeCtrl($scope,$http,Employee,$location,$routeParams) {
    // Get all employee details 
    var data;
    Employee.query().then(function(_data) {
      $scope.employees = _data.data;            
    }); 

    // Get Single Employee Details
    $scope.getSingleEmployeeDetails = function(id) {
      $scope.employee = scope.employees[id];
      $location.path('/editemployee/' + id);
    }   
}

However the issue I am facing is that when the code gets routed to /editemployee/1
for some reason the $scope.employees looses its values.

In other words the form never gets populated with employee details.

What am I doing wrong here ?

runtimeZero
  • 26,466
  • 27
  • 73
  • 126
  • 3
    If you have the same controller type for two views it does not mean they are the same instance. So the issue is each time angular changes route it creates a new instance of your controller which doesn't have any of the information the previous instance had. So you need to use a service to have a singleton that you can store the data on. – shaunhusain Jul 15 '13 at 15:57
  • Actually if you look at this example by John Lindquist, he seems to be using $scope of one controller into another one upon changing the route. How is he able to do that ? http://www.youtube.com/watch?v=5uhZCc0j9RY&list=PLdVInYVbR-1wfi5AnAqhKwXf_PYrscUNU – runtimeZero Jul 15 '13 at 16:00
  • Scope inheritence occurs if both scopes are active at the same time I do totally recommend John's videos though at egghead.io, was my first dip into Angular and I still go back quite a bit. – shaunhusain Jul 15 '13 at 17:00
  • Understanding the details of scope inheritance really just requires a good understanding of prototypical inheritance (particularly if you come from a Class(y) inheritance model). In a nutshell, if you inherit a property from your ancestor then when something tries to read that property on you it will be read from your ancestor, however if assigned directly on your instance it will no longer look to the ancestor to get the value. This has implications where things may seem to "break" in weird ways (work first time). – shaunhusain Jul 15 '13 at 17:10

2 Answers2

0

This has to do with scoping. The employees are loaded into the EmployeeCtrl when it is instantiated. Once you perform a routing event in getSingleEmployeeDetails() that causes a different controller to load with a different $scope. A $scope that is separate from the $scope inside EmployeeCtrl. One easy way around this is to let EmployeeCtrl handle the functionality of loading/displaying all employees and a single employee without routing to a new controller. The pros here is that it makes it easier to share information, and you don't have to reload the single employee information when the user clicks on a single employee because you can share that information more easily. The con is that you don't get back button navigation to navigate between selections of single employees.

The other option is to let the SingleEmployeeCtrl reload the information when it navigates. The pro is you get back button access again, but the con is you load the information twice (once for loading the full list, and twice for loading the employee information again). This also allows the user to bookmark single employee records, but who bookmarks things anymore?

chubbsondubs
  • 37,646
  • 24
  • 106
  • 138
  • Third option service used as singleton and A) returns a promise for the data or B) is $watch(ed) within the controllers. Now it's a choose your own adventure. – shaunhusain Jul 15 '13 at 16:04
  • To keep things simple for the time being I am trying to achieve the first result i.e let a single controller handle things. How do i go about doing this. Since the minute the EditEmployee.html page is loaded, the controller looses its $scope. – runtimeZero Jul 15 '13 at 16:27
  • @JamesHans you can use ng-include directive or maybe ng-if directives to include pages/views when some variable is set to a particular value. This way you would be removing the page entry from the routes and instead of updating $location you would just update your variable. This "breaks" routing/deep-linking as chubbsondubs points out here. To fix the routing you can also listen for a route change event and manually update the variable in your controller ( a bit of a hack though ) – shaunhusain Jul 15 '13 at 17:02
  • While you can do an ng-include. What I was trying to describe is combine the markup from EditEmployee.html into the same file as the html the controller is already using. So you can easily use ng-show="selectedEmployee != null" on the parent div to show/hide it. Of course setting selectedEmployee when the user clicks on the table of employees. Deep linking might not matter. More often it doesn't. – chubbsondubs Jul 16 '13 at 01:29
0

Others have already explained the fact that a new controller (and $scope) are created when you change routes. Also note that $scope.employees is populated asynchronously, when the promise is resolved. What is likely happening is that getSingleEmployeeDetails() is being called before the promise is resolved, so the employees array is empty.

To solve the problem, I suggest a different architecture.

You have two views/pages. Each view in Angular typically has its own controller. Models/data are typically stored in services, and an API to retrieve and manipulate those models/data is made available/public by the service. A controller just glues everything together: it injects the service(s) it needs, and then references only the models/data that the associated view needs.

So, even though your app is simple, I suggest the above approach: one service (which stores your employee objects), two controllers, two views. In particular, put the query() call into your service (so it will be called once, when the service is created) and store your data in the service. The service API should define functions/methods that return a promise that will eventually contain the desired data (list of employees, or just one). The controllers should use those methods to get a reference to the desired data.

See also Should services expose their asynchronicity? for an example of how to store the data in the service.

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • Thanks Mark.. this architecture clears some points for me. I have now included a service but am not sure how to include POST calls in this service. Kindly check my modified question – runtimeZero Jul 15 '13 at 18:25
  • @JamesHans, pass your modified employee object as an argument to an API method you define on the service. That service method will call $http.post() on behalf of the controller. – Mark Rajcok Jul 16 '13 at 01:04