I have a simple controller method that does some action (it controls header of my page). I would like, however, to send a message and confirm that other controllers (such as form controllers on page main body) permit that action.
What I would like to do, is:
from the first controller, send a message "BeforeAction"
in another controller, catch that message, generate a reply that might be context-dependent and somehow deliver that reply to first contoller.
based on reply, either continue my action or drop it.
if no listeners to the message Before Action exist, I would like action to continue seamlessly.
How this can be arranged in angular?
When sharing data between controllers you have 2 options. The better one is probably to create a shared service and inject it into both controllers. In this service you could have a message variable. Controller1 could set the message. Controller2 could have a $watch on the message and act appropriatly when the message value changes.
The other option is to use a custom event. If you use $rootScope.$emit(message) in Controller1 this will travel down the scope chain where you can use $scope.$on(message) to catch it.
I think that for you the service would work better. It woyuld allow you to hold a register of listeners that both controllers could access. For example if Controller2 sets a watch on the message variable save this fact to a register variable. In Controller1 beforeAction you can first check the register. If it's empty proceed, otherwise change the value of the message variable
Related
I have two partials and two controllers and some data in $localstorage. One of them is a header that displays user data and the other is a user profile data entry screen. When that screen saves it updates $localstorage.
When they click OK on the data entry screen, I want the data in the header to update.
Right now I have a $watch over the $localstorage in the header which recopies that into $scope.user (which the partial is bound to), and it's working to keep the header updated, but:
It updates the header character by character as they type. This causes a delay when they type each character, and it's a strange UX.
It updates the header even if they don't click OK to save the changes
I don't know how to hook up an event from one controller to another, and even if I did, I don't know how to say "refresh the model" inside the header controller.
How can I do this?
You aren't really providing any code to show how you are binding to $localStorage, so here is some pseudocode that you can adapt to your implementation:
// -- IN THE DATA ENTRY CONTROLLER --
// at the beginning, get a copy of the $localstorage profile data and assign it
// to the scope variable that will be bound to your view
$scope.profileData = angular.copy($localStorage.get('...'));
// all your changes will be made to the $scope.profileData during editing.
// this will not affect $localStorage since we created a copy.
$scope.onOk = function() {
// on save, make a copy of the edited profile and push that to $localStorage.
// At that point, the watch on your header controller should pick up the changes.
$localStorage.set('...', angular.copy($scope.profileData));
};
Let's say in a service I need to make an http request and store the value, and then if something else uses the same service it could just reuse that stored value, without need of sending a request:
_cachedValue = null
.factory('myService', ($http, $q)->
getFoo: ->
dfrd = $q.defer()
if _cachedValue == null
$http.get('/foo').success (data)->
_cachedValue = data
dfrd.resolve(_cachedValue)
else
dfrd.resolve(_cachedValue)
return dfrd.promise
That kinda works. The problem is - when two consumers simultaneously try to invoke the method, it would send two requests. So I need to send the request only if it never has been sent, and when the second consumer invokes getFoo, it would just wait till the request comes through.
Repeatedly checking every few milliseconds until _cachedValue isn't null - sounds pretty dumb, right? I can't $watch on the value change (there's no $scope inside the service). I can though utilize angular's event bus, and emit an event via $rootScope. But that sounds very overkill, since nobody outside of the service would be interested in listening to that kind of event. Can someone suggest a better way?
You should only set the $http cache property to true.
This will prevent more request if a request is already in progress.
see: https://github.com/angular/angular.js/blob/master/src/ng/http.js#L987
I've been following this tutorial http://draptik.github.io/blog/2013/07/28/restful-crud-with-angularjs/. I implemented a Grails backend with it instead of the Java one in the tutorial.
I've got the data coming back and forth, with one issue. If I create/update/delete a user, I don't see the changes reflected on my user list when I am redirected back. I have to refresh the page to see the updates.
Looking at the network traffic for an edit, it looks like it does a PUT and fires off the GET before the PUT is complete. Assuming this is because $resource returns a promise so things can be done asynchronously. So how do I handle this so that when $location redirects me, my list is up to date?
I'm guessing the options are to wait for the PUT to complete before redirecting/querying for the list, or to somehow manually manage the $scope.users to match the request?
Or maybe this tutorial is just a bad example? Maybe there is a better way to do it (still using $resource)?
Note: I've seen Restangular out there, and I've seen $http with success callbacks, but I would like to understand the situation above.
One way to overcome this issue would be to not redirect to the list page, till you get a callback, and then do a redirect. You can show some busy indicator till that time. The resource call looks like this.
resource.update(config,data,function() { //gets called on success},
function(error) { //gets called on failure});
In real life scenario waiting for the response of update makes sense as you want to handle the error and success scenarios on the same page.
I don't see your code anywhere so i'm just assuming (based on what you wrote and your current problem)
You are probably doing a full (or partial) get each time you changed a user and (re)binding the result to your scope. Doing this in the callback of the resource should actually start the digest cycle angular does to update modified objects. If you had been doing the fetching outside $resource - for example with custom/jquery ajax you would need to execute $scope.$apply()
What i really don't understand you would need to wait for the callback. You already know you added/modified a user. Instead of 'detaching' that user from your scope, modify it, post it to your rest server, then wait for callback, and reinserting it into the scope - why not modify it directly in the list/array you put on your scope?
var users = Users.get(function () {
$scope.users = users.record; // bind the resulting records to the scope
});
$scope.updateUser = function (user) {
resource.update(...); //pseudo
};
Then in your html, you will keep a reference to the currentUser and the div-list will update automaticly.
<div ng-repeat="user in users" ng-click="currentUser=user">{{user.Name}}</div>
<input ng-model="currentUser.Name">
<button ng-click="updateUser(currentUser);">Update</button>
If you don't want to see the update in the list while you type, but only once your callback fires or when you hit the button, would would instead use another ng-model for your input like this:
<input ng-model="tempUser.Name">
And you would then copy the value other in either the updateUser method or in the resource callback like this:
$scope.updateUser = function (user) {
user.Name = $scope.tempUser.Name; // should update automaticly
resource.update(...) // pseudo
}
Hope it helped!
I'm working on an AngularJS app that has a catch all route (eg, .when('/:slug', {...)) which is necessary to support legacy url formats from a previous (non-angular) version of the app. The controller that responds to the catch all tries pulling a related object, and, if not found, redirects to a 404 page using the $location.path method. This works at getting the user to the 404 page, but when the user hits back in their browser it takes them back to the page that forced them to the 404 page in the first place and they end up being unable to escape the cycle.
My question is if there is 1) a better pattern for handling this situation, or 2) if there is a way to reroute the user that doesn't force a history push state in the browser?
You can change the url without adding to the history state, found here under "Replace Method". This is effectively the same as calling HTML5's history.replaceState().
$location.path('/someNewPath').replace();
I haven't found that it's possible to change the view without changing the url. The only method to change the view, that I've found, is to change the location path.
The normal operation of the route system is for the $route service to watch for the $locationChangeSuccess event and then begin loading a route. When it's done loading the template, performing the resolve steps and instantiating the controller it then in turn broadcasts a $routeChangeSuccess event. That $routeChangeSuccess is monitored by the ng-view directive, and that's how it knows to swap out the templates and scopes once the new route is ready.
With all of the above said, it may work to have application code emulate the behavior of the $route service by updating the current route and emitting the route change event to get the view to update:
var errorRoute = $route.routes[null]; // assuming the "otherwise" route is the 404
// a route instance is an object that inherits from the route and adds
// new properties representing the routeParams and locals.
var errorRouteInstance = angular.inherit(
errorRoute,
{
params: {},
pathParams: {},
}
);
// The $route service depends on this being set so it can recover the route
// for a given route instance.
errorRouteInstance.$$route = errorRoute;
var last = $route.current;
$route.current = errorRouteInstance;
// the ng-view code doesn't actually care about the parameters here,
// since it goes straight to $route.current, but we should include
// them anyway since other code might be listening for this event
// and depending on these params to behave as documented.
$rootScope.broadcast('$routeChangeSuccess', errorRoute, last);
The above assumes that your "otherwise" route doesn't have any "resolve" steps. It also assumes that it doesn't expect any $routeParams, which is of course true for the "otherwise" route but might not be true if you use a different route.
It's unclear what of the above is depending on implementation details vs. interface. The $routeChangeSuccess event is certainly documented, but the $$route property of the route instance seems to be an implementation detail of the route system given its double-dollar-sign name. The detail that the "otherwise" route is kept in the route table with the key null is possibly also an implementation detail. So with all of this said, this behavior may not remain functional in future versions of AngularJS.
For more information you could refer to the ng-view code that handles this event, which is ultimately what the above code is trying to please, along with the event emitting code that I used as the basis for the above example. As you could infer from these links, the information in this post is derived from the latest master branch of AngularJS, which at the time of writing is labelled as 1.2.0-snapshot.
I am trying to create a message-board type element in a CakePHP app. This element will be displayed on all pages and views that use a particular layout. I want it to display all the messages in the model, then show the add form when a link is clicked, then return to the updated message list when submitted. All this without affecting the current view/page.
I have my message model/controller/index set up, with a message board element that requests the index action. This works fine. However I am perplexed about how to return back to the original page/action from which the link was clicked. I can't use $this->referer() because that will link back to the add() action; what I want rather is to link to the page/view before that.
Any general pointers on how to achieve something like this?
I would approach this using Ajax, and use an ajax layout.
$this->layout('ajax')
Then you would be able to setup a full stack for processing this, and pass various things in as parameters into the controller actions.
By using Ajax you will not need to worry about passing in the referrer controller / action pair. You can also use the return from this to update the list by calling out to the MessagesController. The added bonus of this is that you can just switch the layout in your actual controllers, thus not having to write any extra code at all.
In your controller, you can check for Ajax
if($this->params['requested']){
$this->layout('ajax');
return $data;
}else{
$this->set('data',$data);
}