Dynamic view names with ui-router - angularjs

I am trying to target dynamic named views in an ng-repeat but can't do so at config phase as views can only be named statically. Is there a way imitate url param matching like '/path/:param' but with view names like views: {'path:param': {...}} ?
I've tried modifying the state config object at run() to see if changing state configuration after config() had any effect:
rootScope.$on('$stateChangeStart', function(e, to, toP, from, fromP) {
//nope
if(toP.itemId) {
to.views['item-'+toP.itemId+'#home'] = to.views['item-:itemId#home'];
delete to.views['item-:itemId#home'];
}
}
plunker: http://plnkr.co/edit/ZkrteD1ls71yd5V10Xub?p=preview

This concept would not be working, in general.
The reason is that stable, solid states should not be driven by the data (which are very dynamic, changing frequently). All states and their views :{} should be defined first. The data (as a list of items) should be injected there, consuming already defined states.
states should be defined once (even if a bit later, once configuration is loaded via $http)
data could change during the application life-time often, frequently. They should be placed into states/views - but not drive them
How to load dynamic states dynamically with $http (using the .run() phase)? Check these with full examples and details:
AngularJs UI-Router - Page Refresh $state.current is now empty
AngularJS - UI-router - How to configure dynamic views
the doc: $urlRouterProvider.deferIntercept(defer)
Small extract from the doc of deferIntercept(defer):
Disables (or enables) deferring location change interception...
So, we can (once) postpone the url / location handling... to the moment, when all states are dynamically loaded. But this feature could be used just once.
Well - all that could still not fit into what we want - place some details into the list.
There is a big detail Q & A, which should give some insight:
How to replace list item with details
Please, have a look at that, because there is detailed explanation and working example...

Related

Angular sidebar search directive

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

Difference between AngularJS UI Router params object specified in state and views

I have just started working on AngularJS and specifically Angular UI Router project. While working on a project I observed that some of team members have specified params option object in view option object of state. when this is the case it doesn't accept optional parameters when passed through ui-sref/state.go.
However I moved this params option object to state instead of view and optional parameters feature started working. I am using AngularJS 1.3.x and AngularJS UI Router version 0.2.13. Here is the sample code to explain more clearly what I want to say :
$stateProvider.state('contacts', {
url:"/user/{userId}/contact"
views: {
'view1': {
....//other options
params :{userId:0,contactId:null}
}
}
...//other options including `controller` and `resolve` options.
});
In above sample code(I have given minimal required information) params object is specified on view1 object instead of on contacts state. Also contactId is the optional non-URL parameter which is passed on one of the use case and not passed in another one. However when I check $stateParams object in the controller specified on state it just shows up userId and not contactId even if I pass it.
I fixed this issue when I moved params option object from view1 object to contacts state object as shown below:
$stateProvider.state('contacts', {
url:"/user/{userId}/contact"
views: {
'view1': {
....//other options
//i have removed the `params` object from here..
}
}
...//other options including `controller` and `resolve` options.
params :{userId:0,contactId:null} //and have put it here.
});
Now I have following questions :
1) What difference does it make by changing where I specify params object. What are the significance if any?
2)Is specifying params object on view altogether wrong configuration? If yes then why UI router doesn't complain and works with parameters specified in URL?
If no then why it doesn't work with optional non-URL parameters?
3) Any specific use cases one would prefer specifying params option object on view object than on state object?
Also another side effect I found when I moved this params option object to state from view object is I am no longer able to bookmark this url or even refresh url in browser. When I do this it redirects me to our home page. Maybe this could be how we are handling this redirection in our project. But just curious to have any pointers why this could be happening?(including how generally this redirection is handled using ui-router) Of course I am going dig deep into our project code to see why this side-effect is happening.
However I would at least like to have answers to my 3 questions(and subquestions) I have asked here.
Neither the guide nor the API documentation say that a named view can have parameters. So I think adding params in the view was just a mistake, and it had no effect whatsoever.
So,
1) What difference does it make by changing where I specify params object. What are the significance if any?
In the views, ui-router doesn't care about it. In the state, it does what is documented in the API documentation.
2) Is specifying params object on view altogether wrong configuration? If yes then why UI router doesn't complain and works with parameters specified in URL? If no then why it doesn't work with optional non-URL parameters?
Yes, it seems to be wrong configuration.
3) Any specific use cases one would prefer specifying params option object on view object than on state object?
No

is it right to do using ui-router to activate menu items. Any advices to help me understand this?

What I did is:
When a menu item is clicked, a action will be done, like deleting a user, sending emails to a group, etc. To this end, for each menu item, I define a ui-router state, and use the state url to activate the state via sref. I thought that a menu action is just a UI component for user to let users to do something, which is just a state of UI.
I was advised that I was using ui-router in a wrong way as a state url can not identify an action. For example, to delete a group of users, the state url can not tell you what group of users have been deleted.
In short, I agree with your manager while being an angular newbie myself. Angular routes are designed for managing different views of your app. I.e. define a route and corresponding view template for each view. If you add application logic into the routes, your application structure gets quickly a mess and difficult to keep clear.
To me it is much more natural that the views are managed by the routes, and each action in each view is handled by the controller of that view. If the actions grow "big", then it is worth refactoring parts of the controller into separate services. If you require some sort of "dynamic HTML" depending on the action, e.g. bootstrap modals are handy for doing that within the current view (see http://angular-ui.github.io/bootstrap/).
E.g. in my current project, I don't actually manually edit the routes at all but let yeoman angular generator to do that for me free of charge - i.e. I instantiate each new view in my dev.env using the following command (more info on this from https://github.com/yeoman/generator-angular)
yo angular:route myNewView
More info on angular philosophy can be read from angular documentation for developers: https://docs.angularjs.org/guide/concepts
You should probably be doing this actions via a method on $scope.
$scope.deleteItem = function (items) {
Service.delete(items);
};
// which is the same as:
$scope.deleteItem = Service.delete;
<a ng-click="deleteItem(item)">Delete This Item</a>
Having it in the URL just seems wrong. I mean what does that look like? www.mysite.com/delete/users?

Re-link an existing $scope?

Let me set some context first;
I am creating a wizard in angular, with these characteristics:
The steps in the wizard are big; each step has it's own controller / $scope / template.
The steps in the wizard are dynamic (step1's output decides whether the next step is step2a or step2b).
Of course, step-state should be preserved. When stepping back from step2 to step1, the data in the step1 view should still be there.
In my implementation, I've found a lot of inspiration in the codebase for ui-router.
Basically my approach is:
When going to a step, create a $scope for this step and use the $controller service to create the controller
$compile the step-template and link this step-template to the step-$scope
This all works out great.
However, I'm having doubts on my implementation to step back in the wizard:
To achieve this, I'm keeping an array of step-$scope objects for each step that has been taken.
Whenever stepping back (for instance from step2 back to step1), I:
look up the $scope for step1
re-$compile the template for step1 and re-link it to the $scope for step1
This seems to work, however:
Why can't I just re-use the compiled/linked template from step 1? (this way, I could keep references to the compiled/linked templates, not to the $scopes)
Is this re-$compiling / re-linking the right approach to take?
Thanks for your time,
Koen
I would use routing, using something along the lines
$routeProvider
.when('/wizard/:stepNr', {...})
(c.f. AngularJS docs: https://docs.angularjs.org/api/ngRoute/service/$route#example)
Your "back" and "next" buttons would just be a href like this:
Prev
Next
The Scopes of all steps will have one common $parent. So, Before going to the next step, you could store the important values there:
$scope.$parent.stepData[currentStepNr] = $scope.stepData;
whenever coming back to a step, the Scope of the left future step is automatically destroyed. All you need to do is
$scope.stepData = $scope.$parent.stepData[currentStepNr]; // load from parent scope into current scope
delete $scope.$parent.stepData[currentStepNr]; // discard in the parent scope
PS: the fact that the Parent Scope is common to all step scopes is a "special case" So you might want to use a Service for storing the data between steps, as suggested by #Bixi
EDIT: In case, the number of the next step is determined by an external web srevice, you can do (assuming a simple http call)
$http.get('/your/service').success(function(responseData){
/* grab nr of next step from responseData;
remember it and
use $location.$path to navigate to #/wizard/nextStep. */
$location.$path('/wizard/' + varContainingNumberOfNextStep);
});
First, how are you storing your data? For this kind of pattern, the best choice would be a service which stores your steps data. This service will be available through all your steps (controllers).
More information about services: https://docs.angularjs.org/guide/services
Next, you're talking about two solutions:
storing data and letting angular construct the screen from scratch again
storing fully compiled templates
The first solution is the angular standard behavior. To maintain a as-lightweight-as-possible DOM, angular removes previous ng-view DOM elements and replaces them with the new ones (new route). No problem here, it's angular's work and it does it well.
The second solution is possible. In my view, storing the compiled DOM would be overkill, instead you could just let it loaded in the browser (but showing/hiding its content via CSS)... But there are some disadvantages unlike method #1 (heavyweight DOM).
I think you should let angular construct your steps using only stored data.
Why not just to have the parent controller with some step flag and make some use of ngInclude capabilities to create new child scope?
mod.controller('WizardParentCtrl', function() {
this.step = '1';
})
Outer HTML:
<div ng-controller="WizardParentCtrl as WizardParent">
<div ng-include="'steps/'+WizardParent.step+'.html'"></div>
</div>
Inner HTML:
<div ng-controller="Step1Ctrl as Step1">
...
</div>
WizardParent will be accessible in the child controllers via scope inheritance, and you can communicate with it in order to save the data or change steps.

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

Resources