Angular - How to Prevent Destroying Scope w/ State Change - angularjs

Disclaimer I am not looking for a simple parent/child ui-router relationship
I am looking for a solution where I am on one page and I can change to any arbitrary state without removing the contents/listeners of the previous state.
Use Case:
User is on a splash page
clicks on login modal
Url changes from /splash to /login
New modal opens up, the splash page contents in the background do not disappear
The idea is that I wouldn't need to be on any specific child state to have this work.

You can do this with UI-Router Extras "Sticky States".
Here is the UI-Router Extras modal demo: http://christopherthielen.github.io/ui-router-extras/example/stickymodal/#/
Add UI-Router Extras:
<script src="https://rawgit.com/christopherthielen/ui-router-extras/0.0.10/release/ct-ui-router-extras.js"></script>
var app = angular.module('plunker', ['ui.router', 'ct.ui.router.extras', 'ui.bootstrap']);
Add a named ui-view for the app and one for the modal
<body>
<div ui-view="app"></div>
<div ui-view="modal"></div>
</body>
Mark your app state as sticky. The effect is that you can navigate from any app.* state to the modal state... instead of exiting that state, it will only "inactivate" it, and it remains in the DOM.
$stateProvider
.state("app", {
template: "<div ui-view></div>",
sticky: true,

You can't be on many states at once. Modals have their own scope, to which you can pass your own stuff and usually modals are not associated with a state switch.
Reconsider that a modal should require a state switch
Modals have their own scope, destroying which doesn't affect the scope of the place from where they were called in any way.

Related

Running ui.router in modal and ui-view

So not certain if this is possible with ui.router, but thought I would add a nice little modal that launches and says welcome to the site, give the user a chance to enter some details and so forth.
Setup is that the base view have two ui-views (mainview and menuview), and I added formview in the modal. I can get it to work so that when the modal opens it loads formview
.state('form-welcome', {
views: {
formview:
{
templateUrl: "/modals/form-welcome",
},
},
//parent:"index"
})
Didn't actually think it would work that easy, but it did, the problem is that as soon as it has loaded, it resets mainview and menuview (and as it is a modal, that means the background goes grey).
I tried to make form-welcome a child of index (the initial view), however, that breaks the whole thing. (index looks as follows)
.state('index', {
url:"/int/index",
views: {
mainview: {
templateUrl: "/pages/index",
controller: "sketchMagController"
},
menuview: {templateUrl: "/pages/top-menu"},
},
})
I can reload all three views (mainview, menuview and formview), and other than a flickering screen its not to bad. But is there a way I can limit state, so that it only changes formview but leaves the other ones alone.
Reason is that I want to change formview through five different screens, and hate flickering pages:)
It seems to me like it should be possible, but I may have missunderstood how it works
UI-router is for changing the application state, not nesting views together. For that purpose you have angular#component.
var app = angular.module('app', []);
app.component('myModal', {
template: '<div ng-show="isShowing">Hello User</div>',
controller: 'modalCtrl'
});
app.controller('modalCtrl', ['$scope', 'modalService', function($scope, modalService) {
//Save showing state in a service (default to false) so you don't popup your modal everytime user visit homepage
$scope.isShowing = modalService.getShowStatus();
$scope.pressButton = function() {
$scope.isShowing = false;
modalService.setShowStatus(false);
}
});
Then using ui-router, declare your index state, with its template as follow
<-- INDEX.HTML -->
<my-modal></my-modal>
<div ui-view='mainview'></div>
<div ui-view='menuview'></div>
The power of ui-router is the ability to replace the ui-views by different template each different state
stateIndex: abstract
stateIndex.stateA: mainview: /home.html
stateIndex.stateB: mainview: /information.html
So ask yourself, will menuview gonna change to different templates in future states? If not, make it a component.
I would try a different approach, and not having this "welcome" modal part of UI router. It doesn't sounds it should be a "state" in an app, where you can navigate to etc.
I would just pop up this welcome modal after your app finished to bootstrap (e.g. in your run() method or after w/e logic you have to start your app), based on your business logic (e.g. show it only one time).
Why not try $uibModal, part of ui-bootstrap? https://angular-ui.github.io/bootstrap/
It sounds like its everything you need, It pretty much has its own state (includes its own view and controller) so piping data in is easy. You can even pass on data captured within the modal with a simple result.then(function(){} . We use it at work and it doesn't reload the state its on.
You'll probably just want to have it be a function that runs automatically in your controller. If you want to limit how often it pops up you can even have some logic for determining when it pops up in the resolve, pass it to your controller and open the modal based on the resolve.
I think the best way to accomplish what you want is to listen on state changed event and fire the modal. The idea is you fire the welcome modal when the first view is opened and then you set variable in localStorage or some service (depends what you want to achive). Here is an example
$rootScope.$on(['$stateChangeSuccess', function(){
var welcomeModalFired = localStorage.get('welcomeModalFired');
if(!welcomeModalFired) {
//fire modal
localStorage.set('welcomeModalFired', true);
}
})

Angular JS - UI-Router: Reload state on click

I'am using Angular JS with UI-Router,
I want to reload state, when we click on the state link from the same state.
I fixed it with the following code,
<a ui-sref="page1" ui-sref-opts="{reload: true}">Page 1</a>
But it is tedious to add ui-sref-opts="{reload: true}" in all the links.
So I want a configuration or settings or code in angular js( or ui-router) to manage this globally. That is if we click on state link from the same state, the state should get reloaded.
Please help me to find a solution.
The way to go here is to adjust $state.go implementation, e.g. like this:
Changing the default behavior of $state.go() in ui.router to reload by default
In general we will use decorator to get control over $state behaviour
.config(function ($provide) {
$provide.decorator('$state', function ($delegate) {
// let's locally use 'state' name
var state = $delegate;
...
see that in action here
Why that should work is described here:
Difference between ui-sref and $state.go in AngularJS UI-Router

Angular ui-router : Creating a navbar that automatically injects itself to all main states and their child states

My navbar should be on top of almost every page (but not all), so I'm trying to load it for some pages using ui-view, but I'm stuck.
What I'm trying to do is to attach my navbar to main states (like contacts, about) and their child states (contacts.list, contacts.detail, etc), but If I do it that way, nav is injected into their main divs so it gets smaller, so I need to attach it into index.html directly.
I copied ui-router's demo app, created a ui-view called navbar, removed navigation part from index.html and created navbar.html.
Here's my plunk : http://plnkr.co/edit/i0DuDcocH04TZadim2Oo
I can't load your plunkr but i already did something like that.
If you want to use a navbar that is independant from the rest you'll need to use views for each part of your main page (like : header / content /foooter).
For that you can have an index like this :
<body>
<section>
<div ui-view="header"/>
</section>
<section>
<div ui-view="content"/>
</section>
</body>
Then define your main state
$state.state('home', {
url:'/'
views:{
'header':{
templateUrl:'navbar.html',
controller:'navbarController'
},
'content':{
templateUrl:'content.html'
controller:'contentController'
}
}
});
In your navbar template you have now a dedicated template and controller so you can do whatever you want with it.
If you need to listen for change event for instance :
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams){
event.preventDefault();
// transitionTo() promise will be rejected with
// a 'transition prevented' error
})
from : http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state
The $rootScope is normal, events are only broadcasted through $rootScope.
If you need to pass data between the view you have to stored your variable in the $rootScope. I searched for other way to do it but it's just to much way simplier and enough to use the $rootScope for this.
If ypou want to add some classes when some state (or children) are active you can use the directive ui-sref-active : http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.directive:ui-sref-active. Directive ui-sref-active-eq for strict equality.
When using views you will have a trouble about using subState if you don't want using your app in full view but just normal state with template and controller. The best for that is to have an intermediate state wit a view define like this :
views:{
'#':{
template:'<div ui-view/>'
}
}
Then you can define children of this state normally without using views.
Though i can really say if that's what you're searching for without loading your plunkr i hope this will help you.
I would have two sets of nested states. For the main.xx group you would inject your navbar and create nested states from there. Then each non-nav partial would have it's own set of states. Here is an example.

UI-Router How to automatically load a nested view within the parent view as a default

I've been playing with UI-Router since ngRoute doesn't quite cover what I needed based on my different layouts requiring multiple nested views. What I can't figure out in UI-Router is how to load default nested views without having to click a link. I created a crude example of what I'm mean in a plunker
Essentially there are two main route within each there is a container which hosts nested views. I want to load a default view into them without have to click a ui-sref.
<h1>Auth Panel</h1> <-- main route 1
Just a container for login/forgot/reset
<hr/>
<a ui-sref="auth.login">Show Login</a><br> <-- can click to load nested view, but want to autoload
How to show automatically /login within the panel <br>
without having to click show login?
<div ui-view></div> <-- child to autoload with /login
Thanks
On your parent state add a param
.state('parentState', {
//...
params: {
autoActivateChild: 'parentState.childState'
}
//...
})
And add this somewhere
$rootScope.$on('$stateChangeSuccess', function(event, toState){
var aac;
if(aac = toState && toState.params && toState.params.autoActivateChild){
$state.go(aac);
}
});
If you wanted to navigate automatically to a default child state when the parent loads, you could trigger a click on that particular ui-sref in your controller.
angular.element("#childElementId").trigger('click');

Angular UI Router Nested State resolve in child states

In an angular app I'm working on, I'd like there to be an abstract parent state which must resolve certain dependencies for all of its children's states. Specifically, I'd like all states requiring an authenticated user to inherit that dependency from some authroot state.
I'm running into issues having the parent dependency not always being re-resolved. Ideally, I'd like to have the parent state check that the user is still logged in for any child state automatically. In the docs, it says
Child states will inherit resolved dependencies from parent state(s), which they can overwrite.
I'm finding that the parent dependency is only being re-resolved if I enter any child state from a state outside the parent, but not if moving between sibling states.
In this example, if you move between states authroot.testA and authroot.testB, the GetUser method is only called once. When you move to the other state and back, it will get run again.
I am able to put the User dependency on each of the child states to ensure the method is called every time you enter any of those states, but that seems to defeat the purpose of the inherited dependency.
Am I understanding the docs incorrectly? Is there a way to force the parent state to re-resolve its dependencies even when the state changes between siblings?
jsfiddle
<!doctype html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.0/angular-ui-router.min.js"></script>
<script>
(function(ng) {
var app = ng.module("Test", ["ui.router"]);
app.config(["$stateProvider", "$urlRouterProvider", function(sp, urp) {
urp.otherwise("/testA");
sp.state("authroot", {
abstract: true,
url: "",
template: "<div ui-view></div>",
resolve: {User: ["UserService", function(UserService) {
console.log("Resolving dependency...");
return UserService.GetUser();
}]}
});
sp.state("authroot.testA", {
url: "/testA",
template: "<h1>Test A {{User|json}}</h1>",
controller: "TestCtrl"
});
sp.state("authroot.testB", {
url: "/testB",
template: "<h1>Test B {{User|json}}</h1>",
controller: "TestCtrl"
});
sp.state("other", {
url: "/other",
template: "<h1>Other</h1>",
});
}]);
app.controller("TestCtrl", ["$scope", "User", function($scope, User) {$scope.User = User;}]);
app.factory("UserService", ["$q", "$timeout", function($q, $timeout) {
function GetUser() {
console.log("Getting User information from server...");
var d = $q.defer();
$timeout(function(){
console.log("Got User info.");
d.resolve({UserName:"JohnDoe1", OtherData: "asdf"});
}, 500);
return d.promise;
};
return {
GetUser: GetUser
};
}]);
})(window.angular);
</script>
</head>
<body ng-app="Test">
<a ui-sref="authroot.testA">Goto A</a>
<a ui-sref="authroot.testB">Goto B</a>
<a ui-sref="other">Goto Other</a>
<div ui-view>Loading...</div>
</body>
</html>
The way I find the ui-router exceptional, is in the behaviour you've just described.
Let's think about some entity, e.g. Contact. So it would be nice to have a right side showing us the list of Contacts, the left part the detail. Please check the The basics of using ui-router with AngularJS for quick overvie about the layout. A cite:
ui-router fully embraces the state-machine nature of a routing system.
It allows you to define states, and transition your application to
those states. The real win is that it allows you to decouple nested
states, and do some very complicated layouts in an elegant way.
You need to think about your routing a bit differently, but once you
get your head around the state-based approach, I think you will like
it.
Ok, why that all?
Because we can have a state Contact representing a List. Say a fixed list from perspective of the detail. (Skip list paging filtering now) We can click on the list and get moved to a state Contact.Detail(ID), to see just selected item. And then select another contact/item.
Here the advantage of nested states comes:
The List (the state Contact) is not reloaded. While the child state Contact.Detail is.
That should explain why the "weird" behaviour should be treated as correct.
To answer your question, how to handle user state. I would use some very top sibling of a route state, with its separated view and controller and some lifecycle arround... triggered in some cycles
Real Short answer is use:
$rootScope.$on("$stateChangeStart")
to listen for any scope changes and do appropriate actions.
Longer answer is, check out the fiddle:
http://jsfiddle.net/jasallen/SZGjN/1/
Note that I've used app.run which means i'm resolving the user for every state change. If you want to limit it to state changes while authRoot is in the parentage, put the check for $stateChangeStart in the authRoot controller.

Resources