Best practices for adding/editing data using AngularJS - angularjs

I am currently developing an SPA application using AngularJS which allows end-users to add & edit orders via a form.
What I would like to know is what is considered the best practice for determining whether the application is in an Edit or Create state?
For example, when I click on a menu item that says 'Add New Order' I would expect a partial view called `order.html' displayed with all entry fields blank. If it's possible, I would also like is to re-use this same view when editing an order with the entry fields pre-filled from the current order being edited.
Do I need to use a service/factory for this that has a property to determine state, for example:
angular.module('app')
.factory('orderService', ['$http', function($http) {
var state = {
addOrder: false,
order: {
orderRef: "",
orderDate: ""
}
};
}]);
Then I could set the addOrder property from the relevant controller according to the edit state, as well as filling out the order property with the details of the order when addOrder: true.
I have looked around the net, unfortunately such examples of this appear to be scarce.

I am currently using a service (DataRepo) which stores all the data you want to keep globally. For example, you have an input where users can type in some side notes regarding their order and they might to see their text again if they navigate back to this very site.
<input type="text" ng-model="DataRepo.orderNotes">
In your controller, you inject your DataRepo service and bind it to the scope:
$scope.DataRepo = DataRepo;
By doing so, user input will be automatically stored into the DataRepo service.

Related

Angular JS - differentiating between a save and update with a common template

I am working on a project with a PHP backend and Angular 1.x on the front end. I have a Listings model and I use a common template to create (add) and update (edit) a Listing.
Since eidt and add both use the same front end template I need to be able to detect when a user is creating and when they are editing. It seems there are several ways to do this:
I could pass a paramater in the ng-submit:
ng-submit="saveListing({{isNewListing}}"
Then I could read the value of the paramter in my controller, but I think this is overkill?
When editing a Listing some variables set for the form auto-fill
$scope.listing.id = x;
Therfore I could just check for a value in the above:
$scope.saveListing = function() {
if(listing.id) {
// update action
} else {
// save action
}
};
Is the second option a sound and non-hacky approach. I am not an Angular pro so although it seems the logical approach to me I want to ensure that I am not hot woring this.
I usually do something similar to the second approach. Since editing means you have to "get" the original record in most cases, the record should exist somewhere in the scope. I use ui-router and have a resolve for the record, which means I can check right at the top of the controller:
$scope.isEdit = record != null;
With a scope variable or similar (e.g. controllerAs vm) you can leverage the fact that you're in "edit mode" and change the UI up a bit. Instead of "+ New" on a button you can have "+ Save".
Hope that helps!
We have a large ERP system with angularJs as a front-end framework, and we are using the "check id" approach.
When updating/edit an item there would be existing id for that item.
I think the second approach is good and I don't see any drawbacks.

Copy button not working, need to link between 3 pages and can't link its id or value directly

Okay so my problem is that i have 3 seperate .html files 1 which loads in all the components for the overall page the second for the modal, and the third contains the actual input field that i need to get its value from.
The input field is nested inside the modal.
So i need to get the value of the input field, which ng-model is read from the database.
the pagination looks like main.html > importer.html > input.html
and input.html contains the value's i want, either its id or its value.
I don't expect you to solve my problem for me, i simply need someone to explain or create a snipet to resolve an id or its value in the same structure so i can reproduce it myself.
can't share source code as its licensed, i hope i have given a clear enough explanation for you to understand the concept.
i already tried every stackoverflow or tutorial thats out there, but these all have only 2 compenents/layers which makes it useless to what im doing.
The kind of question you ask is very specific and vague, which makes it difficult to answer and not very suited for StackOverflow. But I can give a general answer on how to share information across components in Angular: Angular wants you to use Services.
The general idea would be to:
Create a service
Inject the service in both components that need to send/receive the data
Use the service for access to Observables to send/receive data
1. Create the service
ng generate service my-service
2. Inject the service
At the top of both components' .ts files:
import { MyService } from 'src/app/my-service.ts';
In the constructor in both files:
construct(
private svc: MyService
) { }
3. Use Subjects to transmit events
The general idea of an observable/subject is that the receiver of data can subscribe to a stream of events and that the sender can place the next value onto the stream. You can use your service to expose such an observable/subject.
In the service:
import { Subject } from 'rxjs';
class MyService {
public events$ = new Subject();
}
In the receiver's ngOnInit:
ngOnInit() {
this.svc.events$.subscribe(
value => {
// What should happen with the new value?
}
);
}
Place this inside of the sending component whenever the value changes for your input field:
this.svc.events$.next(value);
PS: You could also simplify this depending on your use-case by using the service to expose a simple variable and depend on Angular's change detection.

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

Having a set of checkboxes map to a nested array

I am working on a SPA that pulls in customer data from one $resource call, and gets some generic preference data from another $resource call.
The preference data is sent as an array, which I want to use to populate a series of checkboxes, like so:
<div ng-repeat="pref in fieldMappings.mealPrefs">
<input type="checkbox"
id="pref_{{$index}}"
ng-model="customer.mealPrefs"
ng-true-value="{{pref.name}}" />
<label class="checkbox-label">{{pref.name}}</label>
</div>
When a user clicks one or more checkboxes, I want the values represented in that array of checkboxes to be mapped to an array nested inside a customer object, like so:
.controller( 'AppCtrl', function ( $scope, titleService, AccountDataService ) {
// this is actually loaded via $resource call in real app
$scope.customer = {
"name": "Bob",
"mealPrefs":["1", "3"]
};
// this is actually loaded via $resource call in real app
$scope.fieldMappings.mealPrefs = [
{'id':"1", 'name':"Meat"},
{'id':"2", 'name':"Veggies"},
{'id':"3", 'name':"Fruit"},
{'id':"4", 'name':"None"}
];
});
I have tried setting up ng-click events to kick off functions in the controller to manually handle the logic of filling the correct part of the customer object model, and $watches to do the same. While I have had some success there, I have around 2 dozen different checkbox groups that need to be handled somehow (the actual SPA is huge), and I would love to implement this functionality in a way that is very clean and repeatable, without duplicating lots of click handlers and setting up lots of $watches on temporary arrays of values. Anyone in the community already solved this in a way that they feel is pretty 'best practice'?
I apologize if this is a repeat - I've looked at about a dozen or more SO answers around angular checkboxes, and have not found one that is pulling values from one object model, and stuffing them in another. Any help would be appreciated.
On a side-note, I'm very new to plunkr (http://plnkr.co/edit/xDjkY3i0pI010Em0Fi1L?p=preview) - I tried setting up an example to make it easier for folks answer my question, but can't get that working. If anyone wants to weigh in on that, I'll set up a second question and I'll accept that answer as well! :)
Here is a JSFiddle I put together that shows what you want to do. http://jsfiddle.net/zargyle/t7kr8/
It uses a directive, and a copy of the object to display if changes were made.
I would use a directive for the checkbox. You can set the customer.mealPrefs from the directive. In the checkbox directive's link function, bind to the "change" event and call a function that iterates over the customer's mealPrefs array and either adds or removes the id of the checkbox that is being changed.
I took your code and wrote this example: http://plnkr.co/edit/nV4fQq?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