AngularJS Router invoke function when certain URL is loaded initially - javascript

I am using angular router` to track the state of my web app like this:
when('/', {
controller: "AController",
templateUrl: "APanel.html"
}).
when('/subpage/:id', {
controller: "BController",
templateUrl: "BPanel.html"
}).
And I am using Angular Service to track some shared values:
app.service('stateService', function() {
this.someSwitch = false;
this.someLongDataArray = [x, y, z];
});
Currently, before changing path to \subpage\:id url from AController, I will assign new values to members of the service, so they can be referenced in subpages.
Now the question is, if user directly launching the subpage url \subpage\:id, or hit the refresh button on browser on subpage, BController will be invoked, and I will lost the values in the service which are supposed to be prepared by AController.
I am wondering what I should do in this case. is there any way I can get called when user launch the subpage directly, so I have a chance to prepare the data? (Maybe I can watch for html onload event, but not sure that's the best answer).
Thanks

It appears, BController is dependent on AController.
Ideally, Controller should not contain any data/dom manipulaton, state maintenance. It is simply a glue between view and the $scope model.
Being said so, you need not create any such dependency between controllers. The service can be invoked from both controllers.
OR
If indeed there is a requirement that APanel.html must be loaded/initialized before BPanel.html is loaded, then you must check for some flag in BContoller and redirect user to APanel.html.
like
if(<check some flag>){
$location.path('/');
}
But then you have to find the way to redirect the user again to BPanel.html. I guess this is not a recommended approach.

I am not sure I get your question completely. But if there is a possibility that the user might hit BPanel.html directly then you should do something like this.
if(serviceExists()){
//Do your usual Bcontroller stuff here if the services was initialized
}
else{
//Show a warning/error like "Oops, something is wrong go back to '/'" OR
// Initialize services in BController
}
This should be in your BController if initializing your service before BController is that important. You basically force people to stay on AController.

Related

Angular detect leaving a route.

I'm using Angular 1.5 and ui-router. I want to detect when a user leaves a route. I've currently got something like:
$scope.$on("$stateChangeSuccess", function () {
if (!$scope.flag) {
//...
$scope.do_work(reason);
}
});
this doesn't quite work because when the user navigates to the this route, $scope.flag is set to false and this function incorrectly fires. Is there an idiomatic way to fire a function when a user navigates away from a particular route, but not when they navigate to it?
I'd say use onExit hook of ui-router which you can specify on single state. But you can't access $scope inside it.
To deal with it, I'd say maintain service which will have shareable data. Change that shareable data from onExit hook of your desired state. Then you can access the same service inside your controller as well.
$stateProvider.state("contacts", {
template: '<h1>Dummy Template</h1>',
controller: 'myCotroller',
onExit: function(sharableService){
//... do something sharableService variable...
}
})

Where to store remote data used by directive inside ngView? (AngularJS)

Just for example:
I have directive inside ngView with this structure:
.directive('features', function() {
templateUrl: '.../',
link: function(scope) {
// HTTP req, getting remote data.
// Store the remote data to the scope
}
})
When I change the route and return it back, the directive link option is executed again, the scope is empty. It need to wait some time for data-response from the remote server and then showing the data. I trying to avoid the layout stretching.
I am new to angular, I was read the documentation.
My question is: In this case, where the data is good to be saved? Do I need to make a controller? Or I can just cache it?
Note: This directive repeating an array (remote data) and making "features" HTML layout. This directive will be used in another routes with another behaviors.
I do not need code explanations, I can read docs. I accept terminology.
You could store the data in a service, basically to act as a cache as you mentioned. If you need to have that data available on page reloads (hard refresh, your app is reloaded again) you can store it in a cookie/local storage.
app.service("featuresService",function($http){
var cachedFeatures=null;
return{
getFeatures:function(callback){
if(!cachedFeatures){
$http.get("...").success(function(data){
cachedFeatures=data;
callback(data);
})
}else{
callback(cachedFeatures);
}
}
}
})

Notify a directive about data receiving

I have this simple angular directive:
angular.module("directives").directive("loading", function($rootScope) {
return {
link: function(scope, element, attrs) {
console.log("loading directive", scope, element, attrs);
element.addClass("hidden");
$rootScope.$on("$routeChangeStart", function(event, next, current) {
element.removeClass("hidden");
});
// how do I notify data have been received in the controller? <---
}
};
});
As you can see it allows me to show a loading message to be displayed when the route of the app changes. Like this:
Loading...
My problem is that I dunno how to notify the directive in order to get it to disappear.
Let's say the route /list/ is associated to ListController. When the url /list/ gets hit, the controller will load some data and only when such data have been received I would like the loading message to go away. Clearly the directive have no idea when the controller's data gets delivered so my question remains:
How the heck do I come up with a decent design for this?
To basically simplify the design and structure it in a way, what I do is have a 'messaging' service defined as part of an Angular module that takes care of this. Whenever there's a route change, it sets its most current message to 'Loading…' and when a controller is finished loading (finishes all Ajax requests, for example), it calls a clear() on the messaging service to clear all recent messages.
In your directive (in my case a navbar), you can watch for latestMessages from your messaging service and change the alert depending on what the latestMessage changes to. If its blank, you clear the message and so forth.
A little more work, I guess, but its a lot more structured and modular this way. Plus, since the messaging service is central, it can accept messages from as many modules as you'd want.

angular how to use scope from a different controller

I have a user.list.ctrl and a user.detail.cntr. All the controllers are build as a module and are injected in a "user-module" which I inject in the app.js. (see the complete code in the plunker below)
my controller module
angular.module('user-module', ['user-module.controllers']);
my user-module
angular.module('demo.app', ['user-module']);
In both controllers i inject user-Fctr with data from a REST factory. (works well)
user.list.cntrl has a $scope.refresh()
user.detail.cntrl has a $scope.update()
user.list.cntrl
When I enter a new record, i call the $scope.refresh() so I can refresh the list. (this is working fine)
user.detail.cntrl
When i click a user from the list, the user detail loads in a different view (works ok)
when I update the user.detail, I want to call $scope.refresh() to update the user.list , but it is not working. I cannot call $scope.refresh()
I thought that since I inject the same factory into both controllers I can use each others $scopes.
Any ideas on how I can use $scope.refresh() (or update the list when I update the user.detail.js)
I make a plunker with all the js files (the plunker is not functional, it is only to show the code that I have)
http://plnkr.co/edit/HtnZiMag0VYCo27F5xqb?p=preview
thanx for taking a look at this
This is a very conceptual problem.
You have created a controller for each "piece" of view because they are meant for different activities. This is the purpose of controllers. So that is right.
However, you are trying to access the refresh function, written in one controller, in another one. Taken literally, this is wrong, since then, refresh is out of place either inside the user list controller or the detail controller.
A function that is meant to control (literally) what is happening on a specific piece of view is a controller. - There you are right having a controller for the list and one for the details.
A function that is meant to be shared between controllers must be a service. This is exactly what you want for your refresh function to be.
Whenever you inject the same factory into n controllers, you can't use the scope of every controller. This isn't the purpose of a controller.
However, whenever you inject the same factory into n controllers, you can use its exposed methods.
The problem you have, can be solved as follows:
app.factory( 'sharedFunctions', [ 'factoryId', function sharedFunctions( factoryId ) {
var refresh = function () {
factoryId.getAll(/*your params to query*/)
.success( function ( response ) {
//This will return the list of all your records
return response;
});
};
return sharedFunctions;
}]);
With this factory service registered, then you can inject it to your controllers and whenever you need to refresh, just call the exposed method of the service and plot the new information into the view.
Hope it works for you!
i ended up doing this:
I added in the list.contrl this:
factoryId.listScope = $scope;
since I already have the factoryId (my data service) injected in the detail controller, I can call this:
factoryId.listScope.refresh();
it works but I don't know if this is the best way. any comments?

How to store/communicate global asynchronous data to controllers

The basic premise is this....
I have an application. When the user hits the application, it immediately fetches various information regarding the user from a sharepoint server through an ajax call. And depending on what kind of data is received from the user, the app has to display/hide certain information and set certain settings.
Each controller within the application is heavily dependent on the data that is returned from this sharepoint server.
I have several questions...
First, where should this ajax call be made? Ideally it should be run as soon as possible, so should it be executed in the app.run()?
Second, where should this data that gets returned from the sharepoint server be stored? I read that making a factory for the sole purpose of storing data is not best practice, and it is better to just use the $rootscope. Right now, I am just storing a User object in a factory call "User" which in hindsight I guess is a no no
Finally, I'm not sure if there is a way to suspend the loading of the controllers as they are heavily dependent on this on the data that gets returned, but if there isn't, how would one communicate the information that gets received to the controllers. Would this be a case to use the $broadcast method?
Right now I have a kind of hackish solution. It gets the job done, but I'm pretty sure it is less than ideal
Here is a part of one controller. I am injecting the factory User into it
if (User.HasLoadedUserProps == false)
{
User.registerObserverCallback(hasLoadedProperties);
User.GetUser("1111");
}
else
{
if (User.IsAdmin == true)
//do whatever
}
Once the necessary information has been returned from the ajax call, it calls this
var hasLoadedProperties = function ()
{
if (User.IsAdmin == true)
//do whatever
else
utilities.popupBox("You do not have permission to view this page", "Access Denied");
}
Any wisdom, insight, or advice is appreciated!
First:
When your ajax call should happen depends on a few things, but since you mention that you'd like to defer controller loading until the user data is pulled down, your best bet is to put the call in your service. More on that in my response to your last item. Placing that data in a service also makes it easier to share across controllers, which brings us to the next point...
Second:
Your user data absolutely should go in a service, and absolutely should not go in $rootScope. Think of $rootScope like you do window / globals in JavaScript. You want to avoid using it for much of anything. An exception would be where you really, really need to use events ($broadcast/$emit/$on) but even those cases should be rare.
Finally:
Look into the resolve option for $routeProvider (there are similar options for ui-router if you prefer that route (no pun intended).
This option allows you to defer the instantiation of a controller until a set of promises is resolved. In your case, you should return a promise from your User service, which is resolved once the user data is retrieved.
To help demonstrate these points, I made this simple demo. This code, along with the links to the Angular docs, should be enough to get you going ...
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'beer.html',
controller: 'BeerController',
resolve: {
beer: function(Beer){ //injected into controller once promise is resolved
return Beer.getFavorite();
}
}
})
})
.controller('BeerController', function($scope, beer) { // Will load after 3s
$scope.favoriteBeer = beer; // beer comes from resolve in $routeProvider
})
.factory('Beer', function($timeout) {
var beer = {
favorite: 'porter'
};
beer.getFavorite = function() {
return $timeout(function() { // pretend this is an ajax call
return beer.favorite;
}, 3000);
}
return beer;
});
...where beer.html contains:
<div>
My favorite kind of beer is: {{favoriteBeer}}
</div>

Categories

Resources