How to Share $Scope Data Between States in Angularjs Ui-Router

How do I share $scope data between states in angularjs ui-router?

I created working plunker, showing how to use $scope and UI-Router.

The state definition is unchanged:

$stateProvider
// States
.state("main", {
controller:'mainController',
url:"/main",
templateUrl: "main_init.html"
})
.state("main.1", {
controller:'mainController',
parent: 'main',
url:"/1",
templateUrl: 'form_1.html'
})
.state("main.2", {
controller:'mainController',
parent: 'main',
url: "/2",
templateUrl: 'form_2.html'
})

But each state can have different controller. Why? because each view of each state gets new instance of defined controller. So while we have mainController like the one below, we can be sure, that if we navigate to state 'main.2' it will be instantiated twice.

controller('mainController', function ($scope) {
$scope.Model = $scope.Model || {Name : "xxx"};
})

But what we can see here, is that we check if $scope.Model already exsits... and if not (Parent state) we instantiate it with new intance {Name : "xxx"}.

Well, what I am saying is: only parent state will init the $scope.Model. All others will get that already filled. How? Well here is the answer:

Scope Inheritance by View Hierarchy Only

Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).

It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states.

So, as stated in the documentation. Because our child views are nested in the parent view, the scope is inherited.

Understanding Scopes

In AngularJS, a child scope normally prototypically inherits from its parent scope.

...

Having a '.' in your models will ensure that prototypal inheritance is in play.

// So, use
<input type="text" ng-model="someObj.prop1">
// rather than
<input type="text" ng-model="prop1">.

And that's it. We get inheritance from UI-Router views and angular scopes, and because we smartly used a reference type (Model), i.e. do have '.' dot in ng-model definition - we can share data now

NOTE: having dot '.' in the ng-model="Model.PropertyName simply means, that there is a reference object Model {} with some property: PropertyName

Check the working example here

How to preserve scope data when changing states with ui-router?

You need to under stand how prototypal inheritance works. When a parent puts a property value on the scope with

$scope.value = 'something';

In a child component if you access $scope.value the inheritance chain will find $scope.value.

If the child sets

$scope.otherValue = 'something';

If follows the inheritance chain, doesn't find a value of otherValue and creates a property on the child scope, not the inherited prototype so the parent component and any other children of the parent do not see it.

You can use what is called the dot rule of prototypal inheritance. If the parent creates an object on the scope called something like data

$scope.data = { value: 'something' };

Now if the child puts a property on the data object

$scope.data.otherValue = 'something';

It looks for the data object, finds it in the inheritence chain and because you are adding a property to an instance of an object it is visible to the parent and any children of the parent.

let parent = {  value: 'some value',  data: { value: 'some value' }};
let child = Object.create(parent);
console.log(child.value); // Finds value on the prototype chain
child.newValue = 'new value'; // Does not affect the parent
console.log(parent.newValue);
child.data.newValue = 'new value'; // newValue is visible to the parent
console.log(parent.data.newValue);

How do I share scope data between states in angularjs using two different ui routers?

Use services.Services are singletons, so you can store any variables there.

app.service('sessionService', function($http, settings){
var that = this;
this.userSessionData = null;

this.setUserSessionData = function(sessionData) {
that.userSessionData = sessionData;
}

this.getUserSessionData = function() {
return that.userSessionData;
};
});

Share $Scope data between States

UI-Router supports data (Model) sharing among state families. The detailed explanation could be found here

How do I share $scope data between states in angularjs ui-router?

Where we can see, that we need to introduce a Model, a cluster, a reference object.

// controller of the parent state 'services'
.controller('ServicesCtrl', ['$scope',
function($scope) {
$scope.Model = { prop1 : value1, ...};
}])

Because now each child state will prototypically inherit that reference to $scope.Model... we can access it in any child state controller

.controller('ServiceChildCtrl', ['$scope',
function($scope) {
$scope.Model.prop1 = differentValue;
}])

Check it in action in this working plunker

Share data from parent to child state with Angular UI-router

You can use angular's state resolve to achieve your requirement in a better way. Although there are many choices for it.

Procedure:

  1. When your parent state loads, you query all the people in an API call.
  2. In that response, I am assigning the response to an instance of a service using studentService.addStudents($scope.students); where addStudents is a function in the service.
  3. Now, when you navigate to the detail of a person, I have used resolve which fetches the stored data from the service using the function studentService.getStudents() and returns a person object to the controller.
  4. Use that person object directly in the person controller by injecting the resolve variable

I prefer using resolve. I will tell you why.

Here is the resolve you can use:

resolve: {
student: function(studentService, $stateParams) {
return studentService.getStudents().find(function(student) {
return student.id === $stateParams.personId;
});
}
}

You will add a service studentService or you can extend your own service.

Service:

talentforceApp.service('studentService', function(){
vm = this;
vm.students = []

this.addStudents = function(students) {
vm.students = students;
}

this.getStudents = function() {
return vm.students;
}
});

I added addStudents and getStudents methods to it.

One method add students to array and the other get the data of a studenr.

People Controller revised:

talentforceApp
.controller('People_Controller', ['$scope', '$state', '$stateParams', 'StudentService', function($scope, $state, $stateParams, StudentService,studentService) {

StudentService.query().$promise.then(function(data) {
$scope.students = data;
studentService.addStudents($scope.students); // this will create a students array in service instance
});
}]);

I assigned $scope.students to the service instance.

routes revised:

var TalentForceState_seeProfile = {
name: 'students',
url: '/seeProfile',
templateUrl: 'public/templates/talentforce_template.html',
controller: 'People_Controller'
}

var singleStudent = {
name: 'student',
parent: 'students',
url: '/:personId',
templateUrl: 'public/templates/person_template.html',
controller: 'Person_Controller',
resolve: {
student: function(studentService, $stateParams) {
return studentService.getStudents.find(function(student) {
return student.id === $stateParams.personId;
});
}
}
}

Now, you can use student from resolve into your controller, as a dependency.

person controller revised:

talentforceApp
.controller('Person_Controller', ['$scope', '$state', '$stateParams', 'StudentService',student, function($scope, $state, $stateParams, StudentService,student) {
$scope.student_id = $stateParams.personId;
console.log($stateParams)
$scope.student = student // this line add student to scope
console.log($scope.student)
}]);

Check your student object in the view:

View:

<div ng-controller="Person_Controller">
<h3>A person!</h3>
{{student}}
<div>Name: {{student_name}}</div>
<div>Id: {{student_id}}</div>
<button ui-sref="students">Close</button>
</div>

here is why I prefer to use resolve and its advantages

AngularJS ui router passing data between states without URL

We can use params, new feature of the UI-Router:

API Reference / ui.router.state / $stateProvider

params A map which optionally configures parameters declared in the url, or defines additional non-url parameters. For each parameter being configured, add a configuration object keyed to the name of the parameter.

See the part: "...or defines additional non-url parameters..."

So the state def would be:

$stateProvider
.state('home', {
url: "/home",
templateUrl: 'tpl.html',
params: { hiddenOne: null, }
})

Few examples form the doc mentioned above:

// define a parameter's default value
params: {
param1: { value: "defaultValue" }
}
// shorthand default values
params: {
param1: "defaultValue",
param2: "param2Default"
}

// param will be array []
params: {
param1: { array: true }
}

// handling the default value in url:
params: {
param1: {
value: "defaultId",
squash: true
} }
// squash "defaultValue" to "~"
params: {
param1: {
value: "defaultValue",
squash: "~"
} }

EXTEND - working example: http://plnkr.co/edit/inFhDmP42AQyeUBmyIVl?p=info

Here is an example of a state definition:

 $stateProvider
.state('home', {
url: "/home",
params : { veryLongParamHome: null, },
...
})
.state('parent', {
url: "/parent",
params : { veryLongParamParent: null, },
...
})
.state('parent.child', {
url: "/child",
params : { veryLongParamChild: null, },
...
})

This could be a call using ui-sref:

<a ui-sref="home({veryLongParamHome:'Home--f8d218ae-d998-4aa4-94ee-f27144a21238'
})">home</a>

<a ui-sref="parent({
veryLongParamParent:'Parent--2852f22c-dc85-41af-9064-d365bc4fc822'
})">parent</a>

<a ui-sref="parent.child({
veryLongParamParent:'Parent--0b2a585f-fcef-4462-b656-544e4575fca5',
veryLongParamChild:'Child--f8d218ae-d998-4aa4-94ee-f27144a61238'
})">parent.child</a>

Check the example here

UI Router - how to store data between states without passing them explicitly to $state.go(...)?

1- You can use params option in your route

.state('about', {
url: '/about?isDraft',
params: {
isDraft: null
}
})

2- You can use services as you stated

3- You can store your data in sessionStorage

These are my options.



Related Topics



Leave a reply



Submit