How to implement routing based on the $rootScope variable value - angularjs

I have a requirement to implement a data collection "wizard" app, using AngularJS.
As the user progresses onto the next wizard page, I'm faced with a choice: a) to have each page partial have its own "Next" button, with hg-click explicitly calling the pre-defined next partial page or b) to have just one pair of "Next/Previous" buttons and implement navigation in a more dynamic way, by storing the curtest wizard step in a $rootScope.
My question: If I chose option "b", how what would be the way to implement dynamic routing based on the $rootScope.currentWizardStep value?
Or, maybe, there is a better way to do all this. If You know of such as way, please share :)

You're looking to implement $routParams.
This allows you to name segments of a URL, which then turn into a key/value pair.
from the docs:
// Given:
// URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
// Route: /Chapter/:chapterId/Section/:sectionId
//
// Then
$routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
Then within your controller, you just have to check the URL to determine which page they are on. Using the example given from the docs, if you wanted to identify which chapter they are on, you would do:
var chapter = $routeParams.chapterId;
This would still allow you to keep just one set of 'next/previous' buttons. I don't know how you're incrementing pages (different names, page numbers, etc). But it should be fairly minor to keep track of what pages are next/previous. Especially if you had a master array of all the pages & the order that you expect them to be in.
var wizardPages = {
one: {previous: 'start.html', current: 'one.html', next: 'two.html'},
two: {previous: 'one.html', current: 'two.html', next: 'three.html'},
three: {previous: 'two.html', current: 'three.html', next: 'end.html'}
};
$scope.next = function(){
var current = $routeParams.page;
$scope.template = wizardPages[current].next;
};
$scope.previous = function(){
var current = $routeParams.page;
$scope.template = wizardPages[current].previous;
};
And then your HTML
<div class="" ng-include="template"></div>
And wherever you configure your routes:
$routeProvider
.when('/wizard', {templateURL: 'view/wizard.html'})
.when('/wizard/:page', {templateURL: 'view/wizard.html'});
Your URL would look something like: www.example.com/wizard/one
This would allow you to continue using partials (which would be loaded into your ng-includes)

Populating the $rootScope with statefull data is always a bad idea. Depending on how big your page will be (or to be more precisely: how many subpages you will have) I would even recommend to not use routing at all, but work with the ng-show-Directive and a single Controller, incrementing a value representing the current subpage.
Something like this:
<div ng-show="pageNum == 1">
<h1>First Page</h1>
</div>
<div ng-show="pageNum == 2">
<h1> Second Page</h1>
</div>
<input type="button" ng-click=pageNum++" value="Next Page"/>
Of course this is only possible if you dont need render the Subpages serverside.

Related

Angular - use template as container for multiple templates

Here's a quick question:
Is it possible to have a template in which you have multiple templates and call the one you need, when you need it?
Let me explain this a bit better:
I have a modal that I call this way:
$scope.showErrorModal = ->
errorModal = $ionicPopup.show(
title: 'Issues list'
scope: $scope
templateUrl: './sections/modal/modal.tpl.html'
buttons: [{text: 'Close',type: 'button-assertive'}
])
errorModal.then (res) ->
console.log 'tapped!', res
return
return
as you can see, i'm using an external template.
The problem is that this way i need to create different templates everytime my modal needs to change.
What i'd like to do (if possible), is being able to create various sub-templates inside modal.tpl.html and call them in the right modal.
Here's some example code:
modal.tpl.html:
<div id="error-template">
// here the error-modal stuff
</div>
<div id="success-template">
// here the success-modal stuff
</div>
and from the controller, call them like this, for example:
$scope.showErrorModal = ->
errorModal = $ionicPopup.show(
title: 'Issues list'
scope: $scope
templateUrl: './sections/modal/modal.tpl.html#error-template' //Just to make it clear that i want to use only one part of that file
buttons: [{text: 'Close',type: 'button-assertive'}
])
errorModal.then (res) ->
console.log 'tapped!', res
return
return
Is this pure fiction, or it is possible? Are there any other solutions to solve this type of problems?
Other than reducing the number of network requests, I don't see any real benefit to doing what you mentioned in the question. You may want to use multiple modal directives (errorModal, successModel, etc..) anyway to better compartmentalize your code.
If you want to reduce network requests, there is a $templateCache service that enables you to preload your templates with the first request, in some way like this:
<script type="text/ng-template" id="templateId.html">
<p>This is the content of the template</p>
</script>
You may also want to look at angular ui router which is an alternative router implementation that more easily allows nested and master templates.

Angular js - echoing data across multiple html pages

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.

How can I get the previous url using $ionicHistory.viewhistory?

I want to track the page where the user was previously than the current one.
I was trying to use $ionicHistory.viewHistory() but I can manage to get it. I'm new to angularjs and $ionic so I guess this is a newbie question.
This whole thing is with the purpose of doing a funnel analysis for a single page web app using Piwik but since it doesn't have a proper funnel plugin (thanks Piwik) I'll have to implement it myself. So if you have any advice on the topic it would be great to hear from you.
So what you are looking for is $ionicHistory.backView() or $ionicHistory.backTitle(). If you are using angular states and routing and views.
So in a controller you would call:
$scope.lastView = $ionicHistory.backView()
This gives the the last view you were in. So if you go from A to B it will give you A.
If you want the title of the last view call this in a controller:
$scope.lastViewTitle = $ionicHistory.backTitle()
This gives you a string, which is whatever title you gave the previous view. This would return "My Page" if you called backTitle() after you just left this view.
<ion-view view-title="My Page">
<ion-content>
Hello!
</ion-content>
</ion-view>
You can read more here:
http://ionicframework.com/docs/api/service/$ionicHistory/
If you dont want views but states, you should be able to grab the previous state on $stateChangeSuccess that is broadcast.
$rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
//assign the "from" parameter to something
});
var history = $ionicHistory.backView();//returns an object with data about the previous screen. console.log(history) to see the entire data.
var previous_statename = history.stateName;//compare this
var previous_url = history.url;
var previous_title = history.title;
Use:
$ionicHistory.backView().stateName
which will have this value:
app.tab.contacts

Angular choose html file based on url parameter

I don't know very much about angular, and I'm trying to get the hang of best practices regarding the use of URL routing and states and whatnot. I've got a scenario. It's a simple question, but I'm using it to get a handle on what's available to me.
Say that I have two completely separate web pages for displaying information on Ford cars and Toyota cars. When you access the pages, all you have is the car ID number, so you just hit the url "cars.com/info/id:198273918273". What's the best way, using angular.js, to immediately strip the id number from the url, use it to look up the car make, and display the appropriate html page, all without changing the url displayed at the top of the browser?
you can use functions in your route templateUrl
.when('/your_url/:car_id', {
templateUrl: function(attrs){
//Example of the login yours will be complex i guess :P
if(attrs.car_id == 1) { return 'template_ford.html' }
if(attrs.car_id == 2) { return 'template_toyota.html' }
},
controller : 'someController'
})
and by that your template can be chaged before the page is rendered and no need to send the user to a different url
Assuming that you are using angular-ui router
$stateProvider
.state('car-get', {
url:'/info/{car_id}/',
controller: ['$scope','$stateParams',function($scope,$stateParams){
var car_id = $stateParams.car_id;
// now you can get your car info
// and bind it to your template.
$scope.car = myservice.getcarinfo(car_id) //Here goes your service or your api calls to get car info
}],
templateUrl: 'path_to_detail_car_page.html',
});
Here is a good begginer tutorial for Angular-ui Router

What is a good practice for redirecting in an angular SPA

I am building an angular app and have encountered several instances where I would like to redirect the user to a certain page. But that information about whether a user should be redirected or not is typically received after a server side request.
In the time it takes to do a server request, the original page starts rendering and hence creates a bad UX.
A case in point would be redirecting to login page when user is unauthorized.
Question 1 I know how to handle these cases individually. But was wondering if there is some standard pattern I can follow to solve this issue.
Question 2 Is there a standard pattern to control when to start rendering the page when information is being fetched from server. for instance
my view has
{{user.name}}
and controller has following code:
userService.load_user().then(function(user) {
$scope.user = user;
});
I don't want anything displayed till user is loaded, maybe just a loading sign. Currently i can do it as such:
//controller
userService.load_user().then(function(user) {
$scope.user = user;
$scope.loaded = true;
});
and
<!-- view -->
<div ng-show="!loaded">
<img src="loading.gif"/>
</div>
<div ng-show="loaded">
real code here.
</div>
This gets complicated when I want to wait on more than one requests.
Use the resolve property of the routes. Am assuming you're using ngRoutes or ui-router. Both include the resolve property on their routes.
To add a spinner or something similar while you wait for them to resolve, listen for the view change events within the shell view controller (assuming you have one) and add/remove the spinner accordingly.
By shell controller I just mean the highest level view within which the others are nested. It may or may not have a controller, but usually does. You might have a showSpinner property on that scope:
myApp.controller('mainCtrl', function($scope){
$scope.showSpinner = false;
$scope.$on('$stateChangeStart', function(){
$scope.showSpinner = true;
});
$scope.$on('$stateChangeSuccess', function(){
$scope.showSpinner = false;
});
});
Then you could just use ng-show='showSpinner' or ng-show='!showSpinner' on the spinner html element and the view element respectively.
This is the basic idea. You will probably end up with something more elaborate.
Here is a Plunker. There is a little more going on there (abstract state etc) than you requested, but you will quickly see how the resolve property is used and how the state is diverted.

Resources