87

I have a situation with a form that stretches over several pages (might not be ideal, but that is how it is). I'd like to have one scope for the entire form that gets filled in as you go along, so that if the user goes back and forth between steps it's easy to remember the state.

So I need to do, in very-pseudo-code:

  1. Set $scope.val = <Some dynamic data>
  2. Click a link and be routed to a new template (probably with the same controller).
  3. $scope.val should still be the same value as it was on the last page.

Is somehow persisting data for the scope the right way to go about this, or is there some other way? Can you even create a controller that has a persisted scope between routes, except for saving it in a database of course.

Richard Ev
  • 52,939
  • 59
  • 191
  • 278
Erik Honn
  • 7,576
  • 5
  • 33
  • 42
  • Just as an addition to ganaraj: Here you find a really nice blog entry with a screencast on how to get different controllers to communicate. There are also a couple of helpful jsfiddles to play around with. http://onehungrymind.com/angularjs-communicating-between-controllers/ I hope it helps. – F Lekschas Dec 14 '12 at 16:27

3 Answers3

138

You need to use a service since a service will persist throughout your app's life.

Lets say you want to save data pertaining to a user

This is how you define the service :

app.factory("user",function(){
        return {};
});

In your first controller:

app.controller( "RegistrationPage1Controller",function($scope,user){
    $scope.user = user;
    // and then set values on the object
    $scope.user.firstname = "John";
    $scope.user.secondname = "Smith";
});

app.controller( "RegistrationSecondPageController",function($scope,user){
        $scope.user = user;
        // and then set values on the object
        $scope.user.address = "1, Mars";

    });
ganaraj
  • 26,841
  • 6
  • 63
  • 59
  • 6
    Ahhh! I tried that before but could not get it to work because I used the same controller for both routes, so at each new route the default values would over-write the ones I have entered on the previous page. With two controllers, or with no default values, this no longer happens. Thanks! – Erik Honn Dec 14 '12 at 17:57
  • 1
    What is about `Value Recipe` https://docs.angularjs.org/guide/providers, like `myApp.value('clientId', 'a12345654321x');` is that also persistent between the routes? – The Bndr Jul 22 '14 at 15:14
  • @TheBndr: It should be. Values are just a special form of services as I understand it. – Robert Koritnik Aug 25 '14 at 12:55
  • 3
    Services won't persist data if you reload the page... So if you are in page 4 of your form and reload that page, you will loose all filled data. – danielrvt Sep 08 '14 at 14:31
  • 1
    Nice answer! As a side point, a style guide for those taking on this answer - services that are not themselves constructors (i.e. used as new XX) should be named lowercase camel: https://github.com/mgechev/angularjs-style-guide. – Matt Byrne Oct 01 '14 at 23:40
  • If you want to use one controller across all pages of your form and still set default values, you can define them in the service, e.g. `return { firstname : "John" };` – Superfly May 03 '15 at 01:19
  • can you please give demo with plnkr ? – Nimesh khatri Jul 17 '15 at 12:12
  • One important thing to note here is, when you are redirecting to another page you should not use $window.location.href which is reinitializing the service(not very sure of why). Instead, you should use $location.path(). Took me a lot of time to figure it out. – Suba Selvandran May 17 '19 at 04:58
9

The service will work, but a logical way to do it using only ordinary scopes and controllers is to set up your controllers and elements so that it reflects the structure of your model. In particular, you need a parent element and controller that establish a parent scope. The individual pages of the form should reside in a view that is a child to the parent. The parent scope persists even as the child view is updated.

I assume you're using ui-router so you can have nested named views. Then in pseudo-code:

<div ng-controller="WizardController">
  <div name="stepView" ui-view/>
</div>

Then WizardController defines the scope variables that you want to preserve across the steps of the multi-page form (which I'm referring to as a "wizard"). Then your routes will update stepView only. Each step can have its own templates, controllers and scopes, but their scopes are lost from page to page. But the scopes in WizardController are preserved across all pages.

To update the WizardController scopes from the child controllers, you'll need to use syntax like $scope.$parent.myProp = 'value' or define a function setMyProp on WizardController for each scope variable. Otherwise, if you try to set the parent scope variables directly from the child controllers, you'll end up just creating a new scope variable on the child itself, shadowing the parent variable.

Kind of hard to explain and I apologize for the lack of a full example. Basically you want a parent controller that establishes a parent scope, which will be preserved across all pages of your form.

Matthew
  • 1,630
  • 1
  • 14
  • 19
tksfz
  • 2,932
  • 1
  • 23
  • 25
  • 1
    This would work with ui-router yes (and it's a pretty nice way to design a page like that), but not out of the box. Out of the box the service method is the way to go. – Erik Honn Jun 05 '14 at 09:03
  • 1
    Just to clarify, parent scope properties will always be inherited by the child scope. However, if you want to set a parent scope property from the child scope you need to access it on the parent, otherwise you're creating a new property of the same name on the child scope. Setting a parent scope property can be done by going: `$scope.$parent.myProp = 'hello world';` – mbursill Jul 18 '14 at 00:40
  • 1
    This is a much nicer approach than using a service. Services are singletons, so any state defined there is global to the application. If the user exits the wizard part way through, then re-starts it, there will be left-over state in the service from the previous run - unless you make sure you clean up the service properly at every exit point, but this can quite easily become a substantial maintenance headache! – Dan King Jun 30 '15 at 11:08
  • Much more keen to this answer – Clay Banks Jan 31 '17 at 18:56
  • Services are the first thing to come to mind if you're not using Angular UI Router. If you are using UI Router, though, it has nested resolves that can persist information between nested views. I think that would be a cleaner approach than trying to access parent scope. https://medium.com/opinionated-angularjs/advanced-routing-and-resolves-a2fcbf874a1c – losmescaleros Sep 28 '17 at 17:46
6

The data can be passed between controllers through two ways

  1. $rootScope
  2. Model

The sample below demonstrates the passing of values using the model

app.js

angular.module('testApp')
  .controller('Controller', Controller)
  .controller('ControllerTwo', ControllerTwo)
  .factory('SharedService', SharedService);

SharedService.js

function SharedService($rootScope){

 return{
    objA: "value1",
    objB: "value2"
  }
}

//Modify the value in controller A

Controller.js

function Controller($scope, SharedService){

 $scope.SharedService = SharedService;

 $scope.SharedService.objA = "value1 modified";
}

//Access the value in controllertwo

ControllerTwo.js

function ControllerTwo($scope, SharedService){

 $scope.SharedService = SharedService;

 console.log("$scope.SharedService.objA"+$scope.SharedService.objA); // Prints "value1 modified"
}

Hope this helps.

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
DILIP KOSURI
  • 455
  • 5
  • 10