Running ui.router in modal and ui-view - angularjs

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);
}
})

Related

AngularUI Router - scroll to element on state change

I have searched on StackOverflow for the last five hours and none of the related answers quite solve my problem. I have an UI-Router state that loads a long list of messages generated from a custom directive. This page is linked too in many places pointing to a different message. I want to scroll to the currently selected message.
I can get this to work using $anchorScroll if I surround the call with a $timeout. $timeout(function(){$anchorScroll()};) but if the $timeout is not there a call to $anchorScroll does nothing since the View has not completely loaded.
Here is most of the relevant code.
<message id='message{{message.id}}'
ng-repeat='message in messages'
message='message'
ng-class="{'current':current == 'message{{message.id}}'}" >
</message>
In the controller I set current to $scope.current = $location.hash(). This all works.
If I load the page like #/messages#message100 directly the page will correctly scroll. However, if from a different view I use the a link such as this:
<button ui-sref="message-state({'#':'message{{message.id}}'})>
Go To Message {{message.id}}
</button>
The page will not automatically scroll to the correct anchor since the message list has not been made yet. But by putting the call to $anchorScroll() in a $timeout I can make the page scroll.
I don't like using $timeout for this purpose. I know I am not supposed to manipulate the DOM in a controller like this.
I have tried registering the call to $anchorScroll() with many of the $stateProvider events such as:
$state.$on('$viewContentLoaded', function(event) {
$anchorScroll();
});
But even at the time the $viewContentLoaded fires the message list does not exist in the DOM and the page does not scroll.
IWhat is the best way to make the UI-Router scroll based on the $location.hash().
Even I was facing a similar situation and after days of try and error, I came up with this.
In ui router add an id parameter to the state on which you want to enable the scroll.
$stateProvider.state('index', {
url: '/',
params: {
id: null
}
})
Then in the html use ui-sref
<li><a ui-sref="index({id: 'about-us'})" >About Us</a></li>
At last in the app.run module detect the state change using
$rootScope.$on('$viewContentLoaded', function(event){
if ($state.current.name == 'index') {
if($stateParams.id) {
$anchorScroll.yOffset = 150;
$location.hash($stateParams.id);
$timeout(function(){$anchorScroll()}, 1000);
}
}
});
Hope this helps. Would do a plunkr if needed.

Reload data after state transition

I have a problem after calling the $state.transitionTo method.
$state.transitionTo('example', {}, { reload: true});
When I access my view in the first time, the data is correctly loaded. If I manually go back in my application and try to access the same page, the data is reloaded.
But, when I call the $state.transitionTo, the code inside the controller is not executed and myList is not updated.
Here is the code for the controller.
.controller('ExampleCtrl', function
($scope, $state, $stateParams, Service) {
$scope.myList= {};
var exampleCtrl = $scope;
ProdutorService.findAllData($scope.example.idExample).then(function(data){
exampleCtrl.myList = data;
});
}
I think that it might be related to this bug in the $state.reload():
(bug with controllers reinstantiating right now, fixing soon).
https://github.com/angular-ui/ui-router/wiki/Quick-Reference#statereload
Is there any option to enforce executing the controller code or to call a controller function after the state transition?
I am using angularjs with ionic and cordova. It is a mobile application.
I found the solution.
The responsible for the caching was the ionic framework.
By default, Ionic caches all the views for performance. In order to avoid that, it is needed to add a cache:false in the state definition.
.state('example', {
url: "/example",
templateUrl: 'templates/example.html',
controller: 'ExampleCtrl',
cache: false
})
This way, my data is being refreshed.
you can also use the $ionicView.enter listener to update the data, this way the whole view will not need to be recreated.
Scroll Down to View Lifecycle in this blog post

Am I using states correctly?

I have an angular app with a homepage that shows a list of things. Each thing has a type. In the nav, there are selectors corresponding to each thing type. Clicking one of these selectors causes the home controller to filter the things shown to those of the selected type. As such, I see the selectors as corresponding to states of the home page.
Now, I'd like to map each of these states to a url route: myapp.com/home loads the home page in default (unfilitered) state, myapp.com/home/foo opens the home page with the foo-type selector activated, and switching from there to myapp.com/home/bar switches to the bar-filtered state without reloading the page.
It's that last bit - triggering "state" changes without reloading the page, that's been particularly tricky to figure out. There are numerous SO/forum questions on this topic but none have quite hit the spot, so I'm wondering if I'm thinking about this in the wrong way: Should I be thinking of these "states" as states at all? Is there a simpler approach?
Also, I'm open to using either ngRoute or ui.router - is there anything about one or the other that might make it simpler to implement this?
Using ui-router, you can approach it like this:
$stateProvider
.state('home', {
url: "/home",
controller: "HomeController",
templateUrl: "home.html"
// .. other options if required
})
.state('home.filtered', {
url: "/{filter}",
controller: "HomeController",
templateUrl: "home.html"
// .. other options if required
})
This creates a filtered state as a child of the home state and means that you can think of the URL to the filtered state as /home/{filter}. Where filter is a state parameter that can then be accessed using $stateParams.
Since you don't want to switch views, you inject $stateParams into your controller, watch $stateParams.filter, and react to it how you wish.
$scope.$watch(function () { return $stateParams.filter }, function (newVal, oldVal) {
// handle it
});

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.

AngularUI messageBox with templateURL

I'm using angular-ui / bootstrap $dialog service
It's possible to load a partial inside messageBox? I would take the title and footer, changing only the message param. Thus, it would not be necessary to include the header and footer in partial template.
In this example http://plnkr.co/edit/ttobdpirZlnEQBE3LOeZ, illustrated the behavior I expect by clicking on the 'msg products'.
No. The messageBox method is meant to quickly create message boxes with the consistent look & feel. The idea behind this method is that you can quickly create alert-like messages that have consistent look & feel across the whole application.
As you've noticed the open method is a more flexible version and allows you to create any modal dialog with a partial specified by you.
If you would like to create many dialogs that use the same template you could wrap the $dialog service into your own service, for example:
app.factory('productsDialog', function($dialog){
return function(products) {
return $dialog.dialog({
templateUrl: 'products.html',
controller: 'DialogCtrl',
resolve: {products: function() { return products; }}
});
};
});
and use it like so:
$scope.dlgProduct = function(){
productsDialog($scope.products).open();
};
Here is a working plunk: http://plnkr.co/edit/L2fd7b4y3woLDJmNiN3Y?p=preview

Resources