Angular, show content in a modal for a browsing user but show content on a new page for a new user - angularjs

I'm trying to show the same content (with the same url) in a modal to users who are already on my site browsing, but on a new page for new users.
For example here the user journey on canopy:
Go to https://canopy.co/ and click on a link. Content is loaded in a modal with the url updated to https://canopy.co/products/12309
However go straight to https://canopy.co/products/12309 and it loads it on it's own view, without a modal.
How can I do this with angular? I've tried a load of things with ui-router and multiple views but it ends up being rendered twice.
Thanks for your help.

There are probably several approaches to this. This is mine.
Outline
When the state changes, we want to check whether we could potentially serve the content within a modal over the current view, rather than replacing the current view entirely. We'll define this in our routing config - views that can host a modal will have a modalMaster flag, and views that can be displayed within a modal will have a modalSlave flag.
We'll use ui.router's $stateChangeStart event to intercept the state changes, and cancel them as necessary, this is where we'll open the modal.
Challenges
We need to maintain a knowledge of the state of a modal, if there is one
Preventing a state change also prevents the URL changing, so we need to do that manually
Using the browser back button should close the modal and take us back to the state beneath it.
Code
Note that all code is in coffeescript and jade, I find it much neater. If you need to transcribe, use js2coffee.org.
Router config:
.config ($stateProvider, $urlRouterProvider)->
# Add details on which templates can open models containing which other
# templates.
$stateProvider.state 'shop',
url: '/'
templateUrl: 'shop.html'
modalMaster: true
$stateProvider.state 'item',
url: '/item/{id}'
templateUrl: 'item.html'
modalSlave: true
$urlRouterProvider.otherwise('/');
# Enable handling of url changes ourselves.
$urlRouterProvider.deferIntercept()
The last line is important as it allows us to stop the url changes updating the view later on.
State and URL event listeners
.run ($rootScope, $state, $modal, $location, $urlRouter, $timeout)->
# set some flags for use later.
modalInstance = toStatePrevented = entering = null
# Intercept state changes - if we're in a modal master and we're going to a
# modal slave, open a modal containing the slave state instead.
$rootScope.$on '$stateChangeStart', (event, toState, toParams, fromState)->
if fromState.modalMaster and toState.modalSlave
event.preventDefault()
# set a property to show which state is currently in the modal
$state.current.inModal = toState
entering = true # for url handling
$timeout -> # change url manually
$location.path($state.href(toState, toParams), false)
, 0 # the 0 timeout makes sure the preventDefault doesn't stop url
# change
# open a modal containing the new state, you can merge this with
# modal specific properties like "resolve" etc. if you want them
modalInstance = $modal.open(toState)
# clear our flags when the modal closes - this only uses dismiss so we
# only need "catch" right now.
modalInstance.result.catch(()->
modalInstance = toStatePrevented = $state.current.inModal =
entering = null
$state.go(fromState)
)
$rootScope.$on '$locationChangeSuccess', (event, toUrl, fromUrl)->
event.preventDefault()
# if there is nothing in a modal, just handle as normal
if !$state.current.inModal? then $urlRouter.sync()
# dismiss modal if url changes and modal is visible
else if not entering and modalInstance? then modalInstance.dismiss()
# if we are opening a modal, set a flag so that next time the url changes
# we will dismiss it.
else entering = false
The comments should explain most of this. In practise you will probably need a bit more than what is here - for example, you should make sure the case that a link has been clicked from inside the modal is handled correctly, but this should get you started.
Naturally, I have made a plunker to demonstrate this solution here, but make sure you use the "launch preview in a separate window" functionality to see everything working to its full effect.
Result
Navigating from shop to item opens a modal, but navigating directly to item via a URL opens the item on its own as the ui-view.

Related

Ionic Back button - View does not show effect on localStorage change

I have a simple requirement where out of multiple pages, I have one Settings page in Ionic app, where I am allowing the user to toggle one data(say, language) that is maintained in LocalStorage in the app(via a factory).
This 'language' is used in all views(controllers).
I have Back button on the view but when user changes 'language' through Settings page(I update LocalStorage) and want to go back through IonicHistory back button to the preview view, change does not show up after going back.
"Previous view" uses LocalStorage.Language to fetch data
Back button uses following code:
$scope.goBack = function() {
window.history.back();
}
Can anybody help here or any workaround for this is possible.
Ionic caches views in the current navigation history. In order to update the view everytime you come to a view, you should use $ionicView lifecycle callbacks like .enter, .beforeEnter etc. The code written as part of these callbacks are executed everytime even if the view was cached:
$scope.$on('$ionicView.enter', function() {
// Get your settings data here to reflect it on page everytime
})
For details refer: http://ionicframework.com/docs/api/directive/ionView/

Fire an event when user moves out of speciifc route in AngularJS

I am using AngularJS 1.3. Assume I have created several routes in my application. But when user hits a specifc route/url & then tries to move to another route/url, I want to fire some event. I do not want to fire this event on every URL change.
So only when user comes out of this url http://localhost:9000/data/55677c/edit, I want to fire one function available in XYZ controller.
Here is my scenario:
I have a page which looks like this:
<div class="well">
<button id='edit-btn' type="button" ng-click='saveContent()'>
<div ng-include="'components/grid/comOne.html'"></div>
</div>
components/grid/comOne.html page contains one grid and it has its own controller which takes care of data management of the grid.
This grid is shown in two pages. One in editable mode and one is non-ediatble mode. While user is in editable mode and try to move out of the page without saving the info, I need to fire an event in order to discard ant changes user has made to the grid data.
Please suggest
If the listening controller is a parent controller you could $emit the event.
Or you could have a common service like this:
angular.module('x').factory('CommonLogic', function(){
var pageChangeListeners = [];
return {
listenToPageChange: listenToPageChange
};
function listenToPageChange(callback){
pageChangeListeners.push(callback);
}
function pageChanged(){
for(var i = 0; i < pageChangeListeners.length; i++){
pageChangeListeners[i]();
}
}
});
then when leaving that url (track that via $routeChangeStart) you can call: commonLogic.pageChanged()
In the controller where you want to take action just:
commonLogic.listenToPageChange(function(){..}).
Obviously this should be improved to avoid duplicate registration of the listener ... etc.
I hope I'm not overcomplicating this. Could you describe your use case in more detail ?
I guess you want to use $routeChangeStart:
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
});
You can put this in the scope of your current controller which might be edit as your url says.
From the docs:
$routeChangeStart
Broadcasted before a route change. At this point the route services starts resolving all of the dependencies needed for the route change to occur. Typically this involves fetching the view template as well as any dependencies defined in resolve route property. Once all of the dependencies are resolved $routeChangeSuccess is fired.
The route change (and the $location change that triggered it) can be prevented by calling preventDefault method of the event. See $rootScope.Scope for more details about event object.
Type:broadcast
Target:root scope

Angular - trigger unsavedChanges directive when user clicks on a non-submit button

How do I trigger the angular-unsavedChanges directive to check to see if a form is dirty when a user clicks on a button in a form that is not a submit button? I want a message to pop-up if the user clicks on a button that would navigate them to a different page, if changes have been made to the current form. It seems to work on a refresh and a link to a different page, but not a button that will be loading a different template. TIA.
Without sample code, I can't be sure, but I think that since the directive you are using specifically binds to the $locationChangeStart event, you could try the following :
configure your $routeProvider to monitor url changes ( I suppose it is already what you are doing with your SPA, but correct me if I'm wrong)
$routeProvider
.when('/otherPage', {
templateUrl: 'partials/myOtherPage.html',
controller: 'OtherCtrl',
})
Use you the ng-click on your button to call the $location service (don't forget to pass it to your controller)
$scope.navigateToOtherPage = function() {
$location.path('/otherPage');
}
Since $location fires the $locationChangeStart event right before changing the url, your directive should be able to capture it and proceed with its expected behaviour.

$locationChangeStart never broadcasted; $browser.onUrlChange never called after page load

My angular app is non-standard in that we don't use route provider or UI router. WE use a durandal style of navigation that swaps out views using $http.
locationChangeStart never fires I think due to the fact that we are not changing our URL once in the SPA (though we plan to do so in the future sometimes). I can't test this, but if we remain in the SPA and we change the URL to point to a new view thru code (again, not using routeProvider/UiRouter) should I expect this event to fire?
My main issue is that I cannot handle these events: manual url change, back/forward/history buttons, external link clicks
I see $browser#onUrlChange is the function that broadcasts the $locationChangeStart event but that function only gets called on page load for me, but it seems to be exactly the code that should be getting called.
Documentation:
$browser#onUrlChange
It's only called when the url is changed from outside of angular:
user types different url into address bar
user clicks on history (forward/back) button
user clicks on a link
It's not called when url is changed by $browser.url() method
It says this part also:
NOTE: this api is intended for use only by the $location service. Please use the {#link ng.$location $location service} to monitor url changes in angular apps.
I don't know how to 'use $location service to monitory url changes', so I am listening to the $locationChangeStart event:
$rootScope.$on('$locationChangeStart', function(event, next, current) {
// Breakpoint hit here on page load only
var x = 0;
}
Why doesn't the onUrlChange/$locationChangeStart functions run for me?
thanks!

Updating URL in Angular JS without re-rendering view

I'm building a dashboard system in AngularJS and I'm running into an issue with setting the url via $location.path
In our dashboard, we have a bunch of widgets. Each shows a larger maximized view when you click on it. We are trying to setup deep linking to allow users to link to a dashboard with a widget maximized.
Currently, we have 2 routes that look like /dashboard/:dashboardId and /dashboard/:dashboardId/:maximizedWidgetId
When a user maximizes a widget, we update the url using $location.path, but this is causing the view to re-render. Since we have all of the data, we don't want to reload the whole view, we just want to update the URL. Is there a way to set the url without causing the view to re-render?
HTML5Mode is set to true.
In fact, a view will be rendered everytime you change a url. Thats how $routeProvider works in Angular but you can pass maximizeWidgetId as a querystring which does not re-render a view.
App.config(function($routeProvider) {
$routeProvider.when('/dashboard/:dashboardId', {reloadOnSearch: false});
});
When you click a widget to maximize:
Maximum This Widget
or
$location.search('maximizeWidgetId', 1);
The URL in addressbar would change to http://app.com/dashboard/1?maximizeWidgetId=1
You can even watch when search changes in the URL (from one widget to another)
$scope.$on('$routeUpdate', function(scope, next, current) {
// Minimize the current widget and maximize the new one
});
You can set the reloadOnSearch property of $routeProvider to false.
Possible duplicate question : Can you change a path without reloading the controller in AngularJS?
Regards
For those who need change full path() without controllers reload
Here is plugin: https://github.com/anglibs/angular-location-update
Usage:
$location.update_path('/notes/1');
I realize this is an old question, but since it took me a good day and a half to find the answer, so here goes.
You do not need to convert your path into query strings if you use angular-ui-router.
Currently, due to what may be considered as a bug, setting reloadOnSearch: false on a state will result in being able to change the route without reloading the view. The GitHub user lmessinger was even kind enough to provide a demo of it. You can find the link from his comment linked above.
Basically all you need to do is:
Use ui-router instead of ngRoute
In your states, declare the ones you wish with reloadOnSearch: false
In my app, I have an category listing view, from which you can get to another category using a state like this:
$stateProvider.state('articles.list', {
url: '{categorySlug}',
templateUrl: 'partials/article-list.html',
controller: 'ArticleListCtrl',
reloadOnSearch: false
});
That's it. Hope this helps!
We're using Angular UI Router instead of built-in routes for a similar scenario. It doesn't seem to re-instantiate the controller and re-render the entire view.
How I've implemented it:
(my solution mostly for cases when you need to change whole route, not sub-parts)
I have page with menu (menuPage) and data should not be cleaned on navigation (there is a lot of inputs on each page and user will be very very unhappy if data will disappear accidentally).
turn off $routeProvider
in mainPage controller add two divs with custom directive attribute - each directive contains only 'templateUrl' and 'scope: true'
<div ng-show="tab=='tab_name'" data-tab_name-page></div>
mainPage controller contains lines to simulate routing:
if (!$scope.tab && $location.path()) {
$scope.tab = $location.path().substr(1);
}
$scope.setTab = function(tab) {
$scope.tab = tab;
$location.path('/'+tab);
};
That's all. Little bit ugly to have separate directive for each page, but usage of dynamic templateUrl (as function) in directive provokes re-rendering of page (and loosing data of inputs).
If I understood your question right, you want to,
Maximize the widget when the user is on /dashboard/:dashboardId and he maximizes the widget.
You want the user to have the ability to come back to /dashboard/:dashboardId/:maximizedWidgetId and still see the widget maximized.
You can configure only the first route in the routerConfig and use RouteParams to identify if the maximized widget is passed in the params in the controller of this configured route and maximize the one passed as the param. If the user is maximizing it the first time, share the url to this maximized view with the maximizedWidgetId on the UI.
As long as you use $location(which is just a wrapper over native location object) to update the path it will refresh the view.
I have an idea to use
window.history.replaceState('Object', 'Title', '/new-url');
If you do this and a digest cycle happens it will completely mangle things up. However if you set it back to the correct url that angular expects it's ok. So in theory you could store the correct url that angular expects and reset it just before you know a digest fires.
I've not tested this though.
Below code will let you change url without redirection such as: http://localhost/#/691?foo?bar?blabla
for(var i=0;i<=1000;i++) $routeProvider.when('/'+i, {templateUrl: "tabPages/"+i+".html",reloadOnSearch: false});
But when you change to http://localhost/#/692, you will be redirected.

Resources