I have an AngularJS site, the object-resource I want to show is:
each user has a basic account, that will show in a single page (named basic-page);
user has several sub-account, each sub-account will show in a diffent page (named app-page);
basic-page will show the summer info about the sub-account, so app-page can share the loaded $http data of basic-page is better for code reusing.
As the purpose, I use ui-router define state below:
.state('user', {
url: '/user/{id}',
title: 'User-Page',
templateUrl: helper.basepath('user.html')
})
.state('user.app', {
url: '/{app}',
title: 'App-Page',
emplateUrl: helper.basepath('app.html')
})
Notice that state user.app is the child of user.
What I want is when I enter the user.app, it can reuse the data in user, ecen if it's a different page, that the user need not to contain a ui-view to include user.app's template.
But actually I enter user.app, and it doesn't show the app.html(because I didn't include ui-view in user.html).
Maybe this is not the correct usage of ui-router.
So, how can I share data in different $state? Anyone can give me a detailed example? Thank you.
Sharing data across controllers
Any time you need to share data across states you will need to create a service/factory that you can instantiate in your controllers associated with those states.
The factory will consist of basic getters and setter for the different data you need to share. Just like when you build getters and setters in java to share across classes.
Example Code
.factory('yourFactory', function ($scope) {
return {
get: function () {
return $scope.someValue;
},
set: function(value){
$scope.someValue = value;
}
};
})
Disclaimer: I've not tested this code but it should do the job for getting and setting some values you need to access across your app.
Demo : Working plunker with this approach.
Alternative: 1
This is the "Dirty" alternative, you can set a global variable with $rootScope. It will be accessible everywhere since its global, I strongly advise you don't do this but though I would point it out to you anyway.
Alternative: 2
When a state is "active"—all of its ancestor states are implicitly active as well.So you can build your states considering the parent-child relationship and share data across scopes in hierarchical manner.
Official Docs and working plunker with mentioned approach.
Related
my MusicPlayer.js Angular service has a callback function wrapped in $rootScope.$apply that updates a specific object (musicPlayer.currentPlaybackTime) and is shared to all other controllers ( via applying to $rootScope).
I understand that you'll ideally want to limit any $rootScope pollution, so i'm looking at possible refactoring options that take away calling apply methods to $rootScope but allows my updated object to be shared across multiple controllers.
My research indicates that i'll need to register the other controllers (i.e. PlayerDashboardCtrl.js, PlaylistCtrl.js and AlbumListCtrl.js) that need my currentPlaybackTime object, but i'd like to understand what's the most efficient way of doing this.
Thank you.
var setSong = function(song) {
currentBuzzObject = new buzz.sound(song.audioUrl, {
formats: ['mp3'],
preload: true
});
currentBuzzObject.bind('timeupdate', function() {
$rootScope.$apply(function() {
musicPlayer.currentPlaybackTime = currentBuzzObject.getTime();
});
});
musicPlayer.currentSong = song;
};
The best way to share data between controllers is to make a service/factory and get data using these service from whichever controller you want. You will have to inject this service in all the controllers where you want to access them.
This egghead video will give you a clear understanding: Share data between controllers
Stackoverflow question similar to this: Stackoverflow answers to sharing data between controllers.
Live Demo: Fiddle to show data sharing.
I'd like some advice on how to share some data between two or more controllers in AngularJS.
For now I'm just dealing with two controllers, but in the future I will have more controllers that will also want to use this same data. Right now I have a navigation-controller which is controlling the side navigation and the header. And for ease of understanding, let's say the second controller is called content-controller which is responsible for dealing with all the content.
I want to dynamically load the content based on whatever the user searches for and the search bar is in the side navigation, so this searchTerm needs to be accessible by both controllers. In the future, I would also implement some other features which would probably need to access this searchTerm as well.
In terms of the HTML structure, the content-controller is inside the navigation-controller.
My first thought was to make searchTerm globally available by sticking it in $rootScope, but I'm unsure if this is an efficient/secure way to do it.
My second thought was to take the searching aspects and put them into a service. Inside this service I would put functions which would speak to the API in order to get the necessary data. This would mean on the search bar, I can make the submit search button access the service and run something like FooService.update(searchTerm).
What do you think the best way to deal with this scenario is?
Sharing data between controllers has always been a prominent requirement. You have a couple of options out there :
Factory
Services
You can refer to this answer, for more details upon the differences.
Using services is definitely the better option, since you won't be polluting the root scope with extra variables [That are destined to grow in numbers as your have already mentioned].
A possible way to store your data in services, and access them in controllers and HTML effortlessly can be described as :
Create a service, that will hold all the model variables.
angular.service("dataService", function() {
this.value1 = "";
this.value2 = "";
});
reference that service in your controllers, saving their reference in the scope.
angular.controller("myCntrl1", function($scope, dataService) {
$scope.dataService = dataService;
});
angular.controller("myCntrl2", function($scope, dataService) {
$scope.dataService = dataService;
});
Now in your html, you refer all your modal variables using the service reference :
// Controller 1 view
<div ng-controller="myCntrl1">
<input type="text" ng-model="dataService.value1" />
</div>
// Controller 2 view
<div ng-controller="myCntrl2">
The value entered by user is {{dataService.value1}}
</div>
First of all i don't know whether it's gonna work for you.
You can use local storage.
By using this the same data can be accessed in any controller
Here's an example how it worked for me.
app.controller("loginCtrl", function($scope, $window){
$scope.submit = function(){
$window.localStorage.setItem = ("username", $scope.username);
};
});
app.controller("homeCtrl", function($scope, $window){
$scope.logout = function(){
$window.localStorage.getItem = ("username");
};
});
I have implemented a single page application with AngularJS. The page consists of a content area in the middle and sections assembled around the center that show additional info and provide means to manipulate the center.
Each section (called Side Info) and the content area have a separate AngularJS controller assigned to them. Currently, I communicate via $rootScope.$broadcast and $scope.$on(), e.g.
app.controller('PropertiesController', function ($scope, $rootScope) {
$scope.$on('somethingHappened', function(event, data){
// react
});
});
I then call to communicate with other controllers:
$rootScope.$broadcast('somethingHappened', data);
I have quite a lot of communication happening between the Controllers. Especially if something is going on in the content area, several side info elements have to adopt. The other way around is also frequent: a user submits a form (located in a side info) and the content area and other side info elements have to adopt.
My question:
Is there a better way to handle SPA with heavy controller communication?
The code works fine but it is already getting a bit messy (e.g. it is hard to find which events are handled where etc.). Since the application is likely to grow a lot in the next weeks, I'd like to make those changes (if there are any better solutions) asap.
This is really interesting. Pub/Sub should be a right solution here.
You could add extra order to your project by using Angular services as your MVC's model, and update this model for each change. The issue here is that you should implement an observable pattern inside your service and register to them, in order for this to be live synced. So - we're back to Pub/Sub (or other Observable solution that you could think about...).
But, the project will be better organised that way.
For example - SideInfo1Service will be a service/model. Each property change will trigger an observable change which will change all listeners:
myApp.factory('SideInfo1Service', function($scope){
var _prop1;
return {
setProp1: function(value){
$scope.$broadcast('prop1Changed', value);
_prop1 = value;
},
getProp1: function(){
return _prop1;
}
}
});
You could find those really interesting blog posts about using Angular Services as your MVC's model:
http://toddmotto.com/rethinking-angular-js-controllers/
http://jonathancreamer.com/the-state-of-angularjs-controllers/
And, this post is about observable pattern in Angularjs:
https://stackoverflow.com/a/25613550/916450
Hope this could be helpful (:
You have multiple options in order to avoid broadcasts calls:
Share data between controllers using services like it was mentioned in the comments. You can see how to this at: https://thinkster.io/egghead/sharing-data-between-controllers
Create a main controller for the whole page and child controllers for each section (Content Area and Side Info). Use scope prototype inheritance. For example:
if in main controller you have:
$scope.myObject = someValue;
in child Controllers you can set:
$scope.myObject.myProperty = someOtherValue;
you can access myObject.myProperty from your Main Controller
You can use
$rootScope.$emit('some:event') ;
because it goes upwards and rootscope ist the top level
use
var myListener = $rootScope.$on('some:event', function (event, data) { });
$scope.$on('$destroy', myListener);
to catch the event
Then you have a communication on the same level the rootscope without bubbling
Here is my implemented eventbus service
http://jsfiddle.net/navqtaoj/2/
Edit: you can use a namespace like some:event to group and organize your event names better and add log outputs when the event is fired and when the event is catch so that you easy can figure out if fireing or catching the wrong eventname.
Very important question and very good answers.
I got inspired and created three plunks showing each technique:
Broadcasting: http://embed.plnkr.co/lwSNDCsw4gjLHXDhUs2R/preview
Sharing Service: http://embed.plnkr.co/GptJf2cchAYmoOb2wjRx/preview
Nested Scopes: http://embed.plnkr.co/Bct0Qwz9EziQkHemYACk/preview
Check out the plunks, hope this helps.
I have approximately four main pairs of related directives, simultaneously displayed in my AngularJS UI. Generally speaking, each pair includes a parent list directive on top with an associated detail editor directive beneath it, as shown in this diagram:
Each right-hand list has a many-to-one relationship with the active left-hand selection, and related data must always be displayed. How should I drive related list (the left-to-right association) refreshes?
Currently, each master-detail directive-pair, or "stack", share a service. That service holds the itemState.active (active detail record) and itemState.headers (query master results list). Activity in either the master or detail panel call the service, which directly affect the service state. Then, the master/detail association is operated via simple declarative Angular watches on this common positionService.state; almost no controller code is required. I expect that using the service as the single point of truth here will make it easy for me to integrate near-realtime display in the future, for example via SignalR. This master-detail implementation is only provided here for background, although I welcome improvements:
Master Directive, e.g. position-list.js
templateUrl: "position/position-list.html",
controller: ['$scope', 'positionSvc', function ($scope, positionService) {
$scope.positions = positionService.itemState();
$scope.select = function (position) {
positionService.read(position.id)
};
}
Master Template, e.g. position-list.html
<li ng-repeat="i in itemState.headers" ng-click="select(i)">{{i.title}}</li>
Service, e.g. position-svc.js
this.itemState = {
headers: [],
active: { id: 0 },
pending: httpSvc.pending
};
this.create = function (detail) {
httpSvc.remote("post", detail).then(function (header) {
itemState.headers.unshift(header);
read(detail.id);
});
}
this.read = function (id) {
httpSvc.remote("get", id).then(function (detail) {
itemState.active = detail;
});
}
A similar directive and template exists for the detail. It shares the same service. You get the idea.
Now I'm looking for a maintainable way to handle this left->right eventing following good design practices and well-known AngularJS patterns. I'd like to retain composability of my directives.
To demonstrate I'm not looking for you to do my work for me (and to organize my own thoughts), here are some approaches I came up with, although none have seemed right so far. I'm continuing to document these, check back if you need this section cleaned-up. They are grouped by message path:
leftController -> mainApp -> rightController
part a: leftController -> mainApp
mainApp.$scope.onLeftItemSelected (e.g. via { scope: '&' })
leftController.$scope.$emit within leftController.watch
leftController.$parent.onLeftItemSelected (e.g. AngularJS access parent scope from child controller, but incorrectly creates bottom-up dependency)
part b: mainApp -> rightController
mainApp.$scope.$broadcast
mainApp.rightService.load(leftItemId)
leftController -> mainApp model -> rightController.$watch
share parent scope variable between leftController and rightController
transmit isolated scope from leftController to mainApp using { scope: '=' }; this is Jesús Quintana's solution, below, and here: http://plnkr.co/edit/tyZPziT9VoWDFqHdF8A5?p=preview
leftController -> $route -> rightController
implementing this could be ideal, allowing me to provide deep-linking/bookmarking into my single-page-application, but requires enabling the page to load the parent hierarchy when a child is selected, rather than just the converse (loading the child records of a selected parent item). this requires a greater initial investment than I can currently justify. http://embed.plnkr.co/DBSbiV/preview
leftService -> rightService
Inject the nominationService also into the sibling positionService. Call nominationService.load(positionid) when setting the positionService.activeRecord. Pro: already working. Con: It's wrong. This sibling dependency makes the parent list unreusable.
add a custom Pub/Sub service (https://stackoverflow.com/questions/16235689#16235822, http://jsfiddle.net/ThomasBurleson/sv7D5/)
leftService -> $rootScope.broadcast -> rightService
not ideal, assuming our event string will be unique in the root namespace (but performance issues have been addressed, as discussed here https://stackoverflow.com/questions/11252780#19498009 )
leftService -> mainApp -> rightService
Inject the positionService and nominationService into the mainApp. Add a simple event list to positionService to transmit notification upwards. PRO: this retains the leftService's role as single point of truth for the active record throughout the application (as noted by Martin Cortez, in comments below)
leftService -> $rootScope.$route -> rightController -> rightService
see notes for approach 3
References
pass data between controllers
How to communicate between controllers while not using SharedService between them?
Based on angular pattern design the best way is both directive bind the scope by '=' (Two way binding), and inside the directives make a $scope.#watch to detect changes of the element binding and execute the action function given the variable.
Is the more clean way to communicate inside both directives, angular detect the change and spread out to bot directives.
Sorry , if the answer is useless but the question is not well explain .
The multiple nested views functionality of the ui-router is very nice - you can easily jump from one state of your app to another.
Sometimes you might want to change the URL, but sometimes not. I feel like the concept of state should be separate/optional from routing.
Here's a plunker that shows what I mean. This is a fork of one of the plunkers in the ui-router documentation, with 2 minor changes noted below:
.state('route1', {
url: "/route", // <---- URL IS SHARED WITH ROUTE2
views: {
"viewA": {
template: "route1.viewA"
},
"viewB": {
template: "route1.viewB"
}
}
})
.state('route2', {
url: "/route", // <---- URL IS SHARED WITH ROUTE1
views: {
"viewA": {
template: "route2.viewA"
},
"viewB": {
template: "route2.viewB"
}
}
})
This seems to work - the URL stays the same. Again, how much redundant work is done here? Is this an approved/tested usage?
It would be nice if you could omit the url from a state..
UPDATE: You can omit a url from a state. plunker
Update question: Is this an approved/tested usage?
You can absolutely have a state without a URL. In fact, none of your states need URLs. That's a core part of the design. Having said that, I wouldn't do what you did above.
If you want two states to have the same URL, create an abstract parent state, assign a URL to it, and make the two states children of it (with no URL for either one).
To add to the other answer, Multiple Named Views do not use a URL.
From the docs:
If you define a views object, your state's templateUrl, template and
templateProvider will be ignored. So in the case that you need a
parent layout of these views, you can define an abstract state that
contains a template, and a child state under the layout state that
contains the 'views' object.
The reason for using named views is so that you can have more than one ui-view per template or in other words multiple views inside a single state. This way,
you can change the parts of your site using your routing even if the URL does not change and you can also reuse data in different templates because it's a
component with it's own controller and view.
See Angular Routing using ui-router for an in-depth explanation with examples.