Angular UI nested View with different parameter - angularjs

angular: "1.3.15"
angular-ui-router: "~0.2.13"
TL;DR: How should I provide a value to a nested view?
For brevity's sake I'm going to greatly simplify the application in an attempt to focus on the problem. I have two main views called Customer and Refunds. Each main view has a shared nested view called Notes. The Notes view loads and displays notes relative to the customer at hand using a customerId. The Customer view's route has a customerId, but the Refunds does not. When viewing the Refunds view the notes section is hidden by default. Once the user selects one of the Refunds in the list I want the Notes view to load notes for the customer selected. Since the Notes controller is using $stateParams.customerId it will be empty when loading on the Refunds view. The Refunds controller knows which customerId was selected, but I need some way to tell the nested Notes view which customerId to load.
So far I have come up with a few options, but I'm not sure I like any of them, but here they are.
When the user selects a customer on the refunds page I can navigate to /refunds/:customerId. Don't like that because the page reloads and I have to set the selected item after the page reloads from the stateParams.
After the user selects a customer, use $provide to provide a customerId which gets injected into the NotesController. Don't like that because it's tacky and requires me to do the same thing on the CustomerController.
Caveat: I'm using ControllerAs syntax so scopes don't inherit each other. This prevents me from setting a customerId on the main scope and referencing it in the nested scope.

In your parent scope, store the customerId in the current scope ($scope.customerId = $stateParams.customerId), and on the child scope, get the customer ID from its parent by$scope.$parent.customerId

Related

AngularJS $scope variable loses value after $rootscope.$on function executes

I have an AngularJS single-page application. We display a list of items in a table and users can drill-down to view the detail for a specific item. Some items have child items that can be drilled down as well. To visualize this consider a list of customers that can be drilled down to view customer detail. Each customer may have a list of orders and we can drill down to view order details.
This is the hierarchy of the data flow:
List of customers -> customer detail -> list of customer orders -> order detail
I have no trouble drilling down through this hierarchy but our users have requested "breadcrumbs" to be able to go back to an item's parent, for example, to be able to jump between a specific customer's order detail to the customer detail view.
This being a single-page application has made this a bit tricky but I have come up with a design which I thought would work but I'm having a bit of trouble with it. I'll spare you the gory details of how I am attempting to do that but the root of the problem is I need to pass data from the main view to the child view. I am using $rootscope.$broadcast to do this.
It all boils down to this. I can use the $rootscope.$on function to listen for the event raised by the $rootscope.$broadcast function and grab the data, in this case is that I want to view the detail for customer XYZ. However, I also need to perform some initialization in the child controller's "setup" function which is triggered by calling $(document).ready(setup); When I place a breakpoint in my code it seems that the $rootscope.$on function executes before the setup function executes. To attempt to get around this I tried setting storing that value as $scope.customerID $on function and reading that value in my setup function. For some reason $scope.customerID is undefined when I attempt to read the value. As a result, the "showDetail" function never executes.
MainController code:
function navigate(viewTemplate, itemID) {
$scope.activeViewTemplate = viewTemplate; // Switches views specified by ng-include
// (e.g. viewTemplate = 'customers.html', itemID = 12345)
// If itemID is specified, show detail for that item
if (itemID)
$rootscope.$broadcast('viewDetail', { id: itemID });
}
CustomersController code:
// Calls the setup function when the view loads
$(document).ready(setup);
// Attach the event handler for the viewDetail event
$rootscope.$on('viewDetail', viewDetail);
// Handle the viewDetail event
function viewDetail(event, data) {
if (data)
$scope.customerID = data.id; // <-- Debugger shows this has a value
}
// Perform initialization and display customer detail of customerID is specified
function setup() {
// initialization code goes here
if ($scope.customerID) // <-- Always undefined!
showDetail($scope.customerID);
}
Sorry if this is a bit complex. Basically I need to figure out why $scope.customerID doesn't retain it's value between the "viewDetail" and "setup". I've checked the rest of the code in my Customers controller and $scope.customerID isn't being set/cleared anywhere else. If I can't get this figured out I'll need to scrap my design and come up with something else.

AngularJS: Delete clicked elements parent row

I have a table of users, each row representing a user.
On the end of each row I have a delete link, which when clicked deletes the user from database.
Now I need to update the table to reflect the changes.
I know how I would do this with jQuery, but how do I do it the angular way?
Do I need to add a ng-model attribute to each row?
This is how I have attached the click event:
<a ng-click="deleteUser(user._id)">Delete</a>
I have a <tr ng-repeat="user in users"></tr> printing out the users. I'm thinking if I remove the user from the model, that might be the way to go.
EDIT: I found now this question, which also answer my question.
Yes you are correct. Use ng-repeat to render the list of users and on delete remove the user from the users list. Since it's a two way binding it will dynamically update the tr's.
Also on ng-click, you would need to call a $http service which will tell the database to delete the required user from DB. Use factory/service to create a $http service.

AngularJS: ng-model not getting applied on Controller instantiation

I am creating a web app planner using Angular and I am having some difficulties with a <select> box that is not changing value based on the variable denoted with ng-model.
My architecture is as follows:
I am using ui-router which gives me different view states, one for each page of my planner. The root HTML page has a Controller called MainController. This is where I set up my JSON model, $scope.Master = {} that I want to use throughout the planner. All pages of the planner should inherit this model and continue to modify/add to it.
I then have my 4 pages of the planner like:
Start -> Accounts -> Settings -> Review
Each page has its own Controller that gets instantiated every time I visit the page. On the Start page, I have a <select> box that has ng-model="$scope.Master.Start.selectedAccount" that gets populated dynamically using the StartController (therefore it gets populated every time I come to the Start page).
This <select> works great on the first time to the page, but if I go to Accounts and then come back, the select box is back to the default value, "Please select an account", instead of the selected account that is in the $scope.Master.Start.selectedAccount model that is bound to the <select> box
I thought I could just do something like $scope.$apply or something in order to re-apply the binding to the DOM object, but that just gave me an error saying it is already digesting.
How can I apply the binding to the <select> box after the page has been loaded 2 or more times?
This is probably because every time you go back to your original page, a new controller is instantiated since it was removed from the DOM when you left it originally. Thus $scope.Master.Start.selectedAccount. To save this, you can either
Use a service / factory singleton on the main app to save this value
https://docs.angularjs.org/guide/services#!
Save $scope.Master.Start.selectedAccount as a global variable
https://docs.angularjs.org/api/ng/service/$rootScope
Put that controller on the outside
Im real sorry... I realized I was populating the <select> using ng-repeat instead of ng-options no idea how I managed that... That was the problem

Many active states simultaneously with ui-router

What would you like to be able to do for example is:
I have an active state 'order.detail' in shell, in these details would provide a link in each product line that leads to 'product.detail' state that is also a state that can be displayed in the shell.
But this link should display the state ' product.detail' as a frame in a dialog without changing the current location and maintain active state in the shell intact.
Also the 'product.detail' state, to be used as a main view of the shell, and to allow their reuse, your template should be wrapped by 'div' template of dialogue.
What I mean is, allow consult the details of something without leaving the current screen, and do so using the same existing details screen, or simply allow the 'Drill down' by related data with existing views.
Sharing state in AngularJS
One of the great things about Angular is that's it quite easy to keep track of state via providers.
For example consider one index view containing a paged grid table with many filter options. Clicking on one of the entries will take you to details view of the entry. When the user goes back from the details to the index he/she will expect that the UI state of the grid will be exactly the way they left it: same page, same sort by, same filters applied, same everything. With traditional techniques you would have to fallback on cookies, query params and/or server side state(less) magic, which all feels (and actually is) very cumbersome and error prone.
Provider values are singletons in the world of Angular, so when we inject the instance in one of the controllers, it will always be the same instance. Controllers on the other hand will be recreated each time one is requested.
Example
Register an empty object to keep track of controllers:
myApp.value('formState', {});
Create a controller, inject the provider value and expose it on the scope:
myApp.controller('MyController', function($scope, formState) {
$scope.formState = formState;
});
Hook any property of the provider value to input elements via the ng-model directive.
<input type="text" ngModel="formState.searchFilter"/>
Now every time the user will leave and re-enters this view the state of the UI is kept intact. You can add as many data to the state as you see fit and maybe even share it among multiple controllers if needed.
Provider types
There are different ways to create provider values: factory, service, value, constant and provider. If you want more control over the state, eg state management, you could use one of the other options. More info can be found here.
To dialog or not to dialog
In traditional websites displaying the details in a dialog was a "cheap" trick to keep track of the state in the background. Of course this is still an option with Angular, but there's no need for it. From the UX view, dialogs are "not done" and should be avoided if possible, but it also introduces pains in the areas of responsiveness and printing.
Plunker examples
Some code examples sharing state among controllers/views.
http://plnkr.co/edit/MwJrk5?p=preview
http://plnkr.co/edit/bNJtOP?p=preview

Angularjs scope not retaining value

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 ?
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?
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.

Resources