I'm creating a true SPA application using AngularJS – there is only one view.
For each main feature of the application different panels (directives) will be displayed. Also, there are some common panels that will be shared across features. Below is an image of a contrived example. Notice that there are multiple panels on the view.
The short question is, when the user selects a specific feature (Airline, Hotels, Cars) how do I manage all the different panels that should be displayed and hidden? If this was an application with multiple views I would use AngularJS routing, but not sure if this applies to an application that only has one view.
A couple of things to keep in mind:
All my directives and services should continue to be testable
If possible, I rather not use $broadcast for communication
Should be able to use URL Routes
Are there any recommendations of how to solve this issue?
Given how you've tagged this question with ui-router shows that you're definitely on the right path. What you're looking for can be achieved in a clean manner by using multiple named views.
The short question is, when the user selects a specific feature
(Airline, Hotels, Cars) how do I manage all the different panels that
should be displayed and hidden?
Well given how you will have 3 base states, cars/hotels/airlines, then you will just show the view for that state when it is active, the multiple child views will be shown by default for that state. Look at the basic multiple named view demo:
$stateProvider
.state('cars',{
views: {
'carsFilter': {
templateUrl: 'car-filters.html',//or a common filters templare shared by all your states
controller: function($scope){}
},
'carsSearch': {
templateUrl: 'car-search.html',
controller: function($scope){}
},
'carDetails': {
templateUrl: 'car-details.html',
controller: function($scope){}
},
}
})
You would have that state for airlines and hotels, so handling the state changes is not something to worry about, unless I'm not fully understanding what you asked ;)
If this was an application with multiple views I would use AngularJS
routing, but not sure if this applies to an application that only has
one view.
Well your application is not a single view app. You have that map (could be a base abstract state shared among your child states), you have the airlines/hotels/cars views, as well as the filters/details/search result views.
Related
I'm migrating from angular-route to ui-router and Im trying to figure out what is the best option for build the following scenario:
I have a generic panel wich is composed of two nested elements showed at the same time: A FORM(rendered on the left) and a simple LIST (rendered on the right).
The form is used to edit or even create a new item for the list (this manipulation is made throught a service makes ajax calls)
The list shows all items and I can select one of them and click on "edit", after that the item goes to the form and I'll be able to manipulate/update it.
Here is my code:
.state('mt.demos', {
abstract: true,
url: '/demos',
views : {
'container#' : {
templateUrl: '....demos.html'
}
}
})
.state('mt.panel', {
url: '',
views: {
'form#mt.demos': {
templateUrl: '...form.html',
controller : 'formController',
controllerAs : 'vm',
},
'list#mt.demos': {
templateUrl: '...list.html',
controller : 'listController',
controllerAs : 'vm',
}
}
})
So far I ended up with those 2 options:
Create a service for the shared data (this seems to be the most acceptable answer due to its Singleton behavior -- but look the NOTE 1 at the end of this post.
Create a parent controller so each nested state can access parent properts like the list or the item to be edited. So when I click on "edit item" the listController updates the parent's scope and the formController will be changed with this data.
NOTE 1: Of course to retrieve the list and to update items I already use one service called demoService. This service is used for ajax requests only. So If the best decision is the options 1 showed above, I belive that I should create a new service with a new proposal, right? Or it isnt bad to store some local variable in this same service.
This new service will have the list and the item itself, so when I click on edit I will update the service "item" property?
I think you already have an answer of using a service to store your data.
About a new service with new proposal, while I'm not entirely sure what a 'proposal' means, I'm assuming you are referring to a different list with different fields with your current one?
In that case that would depends on how reusable your code is. If your forms are similar enough, or you can generalize it, then it might be fine to share the same service so you don't have to rewrite the functions. The lists and selected items can be stored as different variables in the service.
Or, you can also store them in different service, and write their common functions in another service.
So I want to have two big pages. I cannot use ui-view because many widgets/ sections of the views are shared (they remain on the screen without loading). The moment a user clicks on a button, the app should move to a different state with merely other sections changing with some animations.
Now, what I tried till now is nested views. But these are only one directional top down architecture. I could not find a way to share views.
I am not using mere ng-include or a directive because I want it to follow a state by state approach keeping intact the history with a URL mapped to it's own template.
The chances are many views are going to reuse/ share the inner views like widgets.
Config
$stateProvider.state('dashboard',{
url : "/dashboard",
views : {
'dashboard' : {
templateUrl: './templates/dashboard.html'
}
}
}).state('dashboard.personalDetails', {
url : '/personalDetails',
views : {
'main' : {
templateUrl: "./templates/personalDetails.html"
}
}
}).state('shipment',{
url : '/personalDetails',
views : {
localView1 : {}
localView2 : //reuse the personal details view
}
})
This is just one example, I basically want to reuse my views as widgets across many pages. Is it possible with ui-router? If yes, then how?
PS: Don't go on this specific example. The actual implementation is for a trading detail page in banking application. And due to policies I cannot share that code structure.
Not really sure what you are asking however you can always create objects to represent similar views and pass those objects to a state definition
var subviews_1={
'widget_1' {templateUrl: '..'},
'widget_2' {templateUrl: '..'},
}
$stateProvider.state('shipment',{
url : '/personalDetails',
views : subviews_1
})
In my angular application I have a global sidebar navigation directive which among other things provides a global search for the user (with bunch of criteria, not just a text field).
Upon searching I'd like to show a page with the search results.
Now, the sidebar is defined in the main app html, what is the best way of sharing the search results data?
Should the sidebar be in charge of performing the search? If so how do I share it's data to the specific page results?
Or on the other hand, perhaps the specific search results page should be in charge of this data? If so how do I connect it with the sidebar search parameters when performing a search?
Any best practices of this scenario are appreciated.
Steps to make your future bright:
Separate your search module in 3 modules: main, sidebar, results
Translate data between each of them with one major SearchResultsService that will:
a) acquire collection of sidebar filters with true or false for each key (key as name for GET param that will be used for passing to search API of your back-end);
b) serialize or deserialize data depending on results module approach;
c) do some pagination;
d) hold or even cache data if you need (for infinite scroll functionality);
sidebar and results will be views of main (child modules), so you will be able to share main controller methods if needed (noob way)
When I was facing implementation of such module I've used black magic to escape $watch and $on event system. If you are young - then use $on that will allow you to notify each of modules about something important (such pagination change, item selection, etc.) and keep them separated same time.
You are free to place your existing sidebar in main module but I'd moved from directive to view with own controller and template.
Directives are used for reusable items either for pasting functionality. But dat sidebar obviously should be defined as separate part of app (aka module) but not as directive.
P.S. Keep your controllers simple.
Google list:
Multiply satisfection
Your golden chest
Root of AngularJS evil
Angular services are substitutable objects that are wired together using dependency injection (DI). You can use services to organize and share code across your app.
https://docs.angularjs.org/guide/services
I've worked with many languages and environments (predominately iOS & C#) and have always managed to avoid working with scripting languages. That avoidance streak has come to an abrupt end: as a huge angularjs project was thrown in my lap - now I'm scrambling to understand this very strange world. Some features are really cool, other techniques have me thoroughly baffled. I've spent weeks reading tutorials, studying examples and I still cannot solve a relatively simple problem regarding best practices and structure of the code.
This is what I need to do: I have a form, where the user will input data (for argument's sake, its two fields of number type.) I need to have a banner at the top of the page with the sum of the two input fields - that by itself is relatively easy - but the problem for me, is repeating this banner on subsequent pages.
Home page will contain links to:
page 1
page 2
The link to page 2 will not be available until the user inputs data on page 1, forcing the user to visit page 1, first. The banner element needs to be a separate file. Page 2 is a passive display of the data, Page 1 is only page that can actively edit the data.
page 1: would look like this --
banner.html (sum of fields A & field B)
input field A
input field B
page 2:
banner.html (sum of field A & field B)
Lorem Ipsum ....
What's the best way to achieve this task?
You can have an index page with the banner on top, and partials using the same controller. The value of the banner will be a controller variable.
To use partials, inside the index page, you'll need to include the ngRoute module, and the script tag linking to it.
You'll have a div like this.
<div ng-view=""></div>
You'll have a partialRoutes.js file looking something like this.
myApp.config(function($routeProvider){
$routeProvider
.when('/',{
templateUrl: './partials/things.html'
})
.when('/stuff',{
templateUrl: './partials/stuff.html'
})
.when('/otherstuff',{
templateUrl: './partials/otherstuff.html'
})
.otherwise({
redirectTo: '/',
})
});
When you include ngRoute it will look something like this.
var myApp = angular.module('myApp', ['ngRoute']);
Here are the docs for ngRoute. Hope I helped.
https://docs.angularjs.org/api/ngRoute
Personally, I would have a service, lets call it userData. Your inputController would then write the details of the inputs to the userData service.
Your bannerController would then be notified of changes to the data in the userData service (via $watch or $broadcast)
This way when you switch pages the bannerController should not change and will still display this information
Notes:
This relies on you using some kind of AngularJS routing technique such as NGroute or UI Router. If a hard page navigation is made then the userData will have to be stored server side.
It would probably be better for the banner to stay outside any ui-view so that it is unaffected by navigation, but if it is then as the userData service will still be alive and holding the correct data when it is recreated it will have access to the same data
If both pages have same controller, then $scope can be used to achieve this. If pages have different controller, $rootScope can be used to share variables.
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