In a big application, i now need to access some data (json api call) from asynchronously (before, those data were accessed synchronously).
I would like not to have to change components implementation to now handle this async behaviour (too risky).
I thought about $routeProvider's "resolve" feature (with promises) that helps to abstract this async behaviour out from the components/controllers.
Unfortunately i am absolutely not using routing in any way.
Is there an implementation for that? If not, where could i start?
EDIT:
how i was before (jsonData were not loaded synchronously but it was transparent thanks to systemJS features, SystemJS, Which we are throwing in garbage now, hence the question):
https://plnkr.co/edit/BSreVYNAr7sijYwEpb5G?p=preview
How i am now:
https://plnkr.co/edit/WnX0ynOqkZsmEzIxl1SI?p=preview
Watch the console to see an example of the problems that can occur now.
I kind of made it work by going like that, but i'm not completely satisfied with it (watch the config block):
https://plnkr.co/edit/P9Rv6rnboFlaFZ0OARNG?p=preview
$provide.value('$provide', $provide);
$routeProvider.otherwise({
template: '<test></test>',
controller: function ($provide, myJsonData1) {
$provide.value('myJsonData', myJsonData1);
},
resolve: {
myJsonData1: function(){
return getMyJsonData();
}
}
});
Hope this helps :)
Related
I have 2 states in coffeescript...
stateProvider.state 'test',
...
resolve:
user: (LongRunning)->
LongRunning.authenticate().then ->
console.log("We are authenticated!")
stateProvider.state 'test.child',
...
resolve:
other: (AfterAuth)->
AfterAuth.soSomethingWithAuth().then ->
console.log("We are done!")
Of course this doesn't work because the child resolve is kicked off before the parent's auth method has been resolved. This means the second function won't be authenticated and cause the entire state to fail.
Now it doesn't need to necessarily be part of the State path but it needs to be fully completed by the time the resolve functions are called.
How would I make sure the function from parent is fully resolved before calling the method in child?
Bad (?) Solution
The one answer I have been able to come up with is to use the manual bootstrap process. However, this is tedious since I would need to rewire all my services. Is there anyway I can do it inside Angular?
Do you use AngularUI Router for Angular 2 or AngularJS? I think that is AngularJS on the fact that you use coffeescript and AngularUI Router. This is Angular tag not AngularJS.
Anyway in AngularUI Router one resolver can depends on another one. Something like that:
function firstStep($stateParams, resolveStatus) {
return $q.resolve();
}
resolve: {
firstStep: firstStep,
secondStep: ['firstStep', function (firstStep) {
...
}]
}
Angular doesn't provide any authorization/access permission on routing (I'm talking default Angular route 1.x and not beta 2.0 or UI route). But I do have to implement it.
The problem I'm having is that I have a service that calls server to provide this info and returns a promise. This data however is only obtained once and then cached on the client, but it still needs to be obtained once.
I would now like to handle $routeChangeStart event that checks whether next route defines a particular property authorize: someRole. This handler should then get that data using my previously mentioned service and act accordingly to returned data.
Any ideas beside adding resolves to all my routes? Can I do this centrally somehow? Once for all routes that apply?
Final solution
With the help of accepted answer I was able to implement a rather simple and centralized solution that does async authorization. Click here to see it in action or check its inner working code.
The most simple way is to deal with current route's resolve dependencies, and $routeChangeStart is a good place to manage this. Here's an example.
app.run(function ($rootScope, $location) {
var unrestricted = ['', '/login'];
$rootScope.$on('$routeChangeStart', function (e, to) {
if (unrestricted.indexOf(to.originalPath) >= 0)
return;
to.resolve = to.resolve || {};
// can be overridden by route definition
to.resolve.auth = to.resolve.auth || 'authService';
});
$rootScope.$on('$routeChangeError', function (e, to, from, reason) {
if (reason.noAuth) {
// 'to' path and params should be passed to login as well
$location.path('/login');
}
});
});
Another option would be adding default method to $routeProvider and patching $routeProvider.when to extend route definition from default object.
ui-router have a lot of events that you can easy manipulate. I always use it.
State Change Events have everything you need. Something like this will be implement in the AngularJS 2.x.
But if you are looking the solution for native Angular 1.x.y router this solution will not help you. Sorry
If you can use ui-router, you could do this:
.state('root', {
url: '',
abstract: true,
templateUrl: 'some-template.html',
resolve: {
user: ['Auth', function (Auth) {
return Auth.resolveUser();
}]
}
})
Auth.resolveUser() is just a backend call to load the current user. It returns a promise so the route will wait for that to load before changing.
The route is abstract so other controllers must inherit from it in order to work and you have the added benefit that all child controllers have access to the current user via the resolve.
Now you catch the $stateChangeStart event in app.run():
$rootScope.$on('$stateChangeStart', function (event, next) {
if (!Auth.signedIn()) { //this operates on the already loaded user
//user not authenticated
// all controllers need authentication unless otherwise specified
if (!next.data || !next.data.anonymous) {
event.preventDefault();
$state.go('account.login');
}
}else{
//authenticated
// next.data.roles - this is a custom property on a route.
if(!Auth.hasRequiredRole(next.data.roles)){
event.preventDefault();
$state.go('account.my'); //or whatever
}
}
});
Then a route that requires a role can look like this :
.state('root.dashboard', {
//it's a child of the *root* route
url: '/dashboard',
data: {
roles: ['manager', 'admin']
}
...
});
Hope it makes sense.
I've approached this issue many times, I've also developed a module (github).
My module (built on top of ui.router) is based on $stateChangeStart (ui.router event) but the concept is the same with the default ng.route module, it's just a different implementation way.
In conclusion I think that handling routing changing events is not the good way to perform an authentication checking:
For example, when we need to obtain the acl via ajax the events can't help us.
A good way, I think, could be to automatically append a resolve to each "protected" state...
Unfortunately ui.Router doesn't provides an API to intercept the state creation so I started my module rework with a little workaround on top of $stateProvider.state method.
Definitively, I'm looking for different opinions in order to find the correct way to implement a Authentication Service in AngularJS.
if are there anyone that is interested in this research... please, open an issue on my github and the discuss
Having created some basic web applications with Angular, still learning and discovering ...
I need to create a Angular application which depends on various parameters used in the application. For example various label string we will retrieve through REST... These labels are used on every screen of the application, including the first screen that is shown on start-up...
I'm looking for a 'proper' / 'good' / 'angular' way to init the application waiting for the result of a sort of 'init-application' rest-call - due to the fact that the result of this rest call contains data the application will need from start on, I have found an example approach below ...
https://blog.mariusschulz.com/2014/10/22/asynchronously-bootstrapping-angularjs-applications-with-server-side-data
Are there any more ideas / thoughts / useful links ...
Thanks!
I would suggest you to explore the 'resolve' property of ngRoute(if you are using ngRoute) or UI-router state:-
While using $routeProvider:-
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}
}
})
Or, if you are using UI Router :-
$stateProvider.state('root',{
abstract:true, // this is abstract statte, controller is in child states.
templateUrl:'/assets/root-module/partial/main/main.html',
resolve : {
securityContext : function($http){
return $http.get("/security/context");
},
token : function(securityService,securityContext){
securityService.setToken(securityContext.data.token);
console.debug(securityContext.data.token);
return securityContext.data.token;
}
}
});
In either of the approaches, just make sure that you DO NOT provide 'ng-controller' in your HTML templates. Rather provide the controller while defining your route|state as the above example suggests.
The properties being "resolved" - if they are promises, the controller (hence the view) won't be initialized until the promise is resolved.
I highly recommend you to explore UI Router state hierarchy approach that allows you to share 'resolved; proerties to the child states.
I am having a very bizarre situation that I can't seem to figure out. Here are my routes:
$routeProvider.when(/application1/:type, {
templateUrl: "application1/index.html"
})
.when(/application1/:type/:folder, {
templateUrl: "application1/index.html"
})
.when(/application2, {
templateUrl: "application2/index.html"
})
.when(/application3, {
templateUrl: "application3/index.html"
});
For some reason when I use $location.path on application2 and application3 it works fine and updates right when i call it. However anytime I switch to anything on application1 it takes about 3-5 seconds, then starts to load. When I refresh the page on application1 it works right away so I don't think it is the code that is associated with it. How can certain routes behave so differently?
UPDATE: I ran a profiler on my application: It seems to be take 5+ seconds to run $apply and/or $digest when loading a route in application1. What can be ways for me to speed this process up?
I had the same problem that routing worked when starting but becomes slow at doing specific routes. Since it worked when started i thought $route.reload(); might do the trick.
$route.reload();
$location.path("application1/");
This fix worked for me. Don't ask me the detailed explanation though.
I just had this issue happen to me. It turns out it was because my function that was calling $location.url was called from jQuery ".on" (jQLite actually). I had forgotten about how with AngularJS if you are called from something outside the $digest cycle you need to call $applyAsync (or $apply) to do "AngularJS stuff". Maybe if it wasn't for my timeout timer it would have not worked at all (I think when that kicked off that's when the new view finally was processed).
The fix in my case was to change this (this is CoffeeScript but same sort of deal in pure JavaScript):
element.on 'hidden.bs.modal', () ->
stuffIncludingLocationUrlCall()
to this:
element.on 'hidden.bs.modal', () ->
scope.$applyAsync ->
stuffIncludingLocationUrlCall()
Now the view change from $location.url happens really fast.
Edit: Here's a Javascript & jQuery version for those that come across this, as I did...
$(element).on("click", function() {
// any processing or other logic you need goes here
$scope.apply(function() {
$location.path("new/path");
});
});
EXTJS 4.1
I have run into a bit of a conundrum and would love to hear some other developers input on this scenario regarding ExtJS events and controller methods. I understand this topic has been covered and debated on numerous occasions but I feel that it could use a bit more delving into.
As is standard practice, I define event listeners in my Controller's init() method declaration as follows:
Ext.define("My.controller.Awesome", {
init: function(application){
/** Observe some Views */
this.control({
'my-awesome-view #saveButton':{
click: this.saveMyData
}
});
/** Listen for Events, too! */
this.on({
showAwesomeView: this.showMyAwesomeView
});
/** Application events, what fun! */
this.application.on({
showThatAwesomeView: this.showMyAwesomeView
});
}
Now, as I merrily go about coding some functionality in some other controller, I find myself needing to call showMyAwesomeView in My.controller.Awesome. I can do this in a few different ways...
Ext.define("My.controller.Other", {
/* ... class definition */
someImportant: function(){
// I can do this (Approach #1)
this.application.getController("Awesome").showMyAwesomeView();
// Or I can do this (Approach #2)
this.application.getController("Awesome").fireEvent("showAwesomeView");
// Last but not least, I can do this (Approach #3)
this.application.fireEvent("showThatAwesomeView");
}
});
To me, Approach #3 feels the most 'right'. My problem is that if I haven't instantiated the My.controller.Awesome before, the init() method has not been run yet and therefore there are no listeners established, so the fired event goes off into mystery land never to be heard of again.
I have overloaded Ext.application.getController() to call controller.init() before returning controller, therefore a controller has its init method called as soon as it is loaded (typically as a dependency in another controller). Is this bad?
In order to save on load time (my application is quite large) my controllers and their dependencies are loaded on an as-needed basis. Therefore, the majority of my controllers (and views, and data stores) are not instantiated when my application first boots therefore not init()'ed which makes firing application-wide events quite cumbersome.
I feel like I may be missing something big here, or maybe I just need to bite the bullet and ensure my controllers have been init'd before firing events. I suppose I could also put an absolutely horrific number of event listeners in the main application file and handle init'ing controllers before calling their methods accordingly, but this seems very sloppy and difficult to maintain.
Any input would be greatly appreciated, thank you for your time!
Approach #3 (this.application.fireEvent('showThatAwesomeView')) is a great solution. The use of application events results in controllers that have no assumptions about what other logic may be added or removed from the application related to this event.
Regarding your concern about controllers having been instantiated in time to be correctly bound to events, use of the Ext.app.Application controller will eliminate this. The App controller initializes all specified controllers when the App initializes. You noted a concern about start-up time related to the number of controllers. I have worked on many single page apps that have dozens and even hundreds of controllers in some cases. Keeping any init logic to a minimum should reduce any noticeable slowdown. Minified and combined scripts in place of on-demand loading has worked well to keep app start-up very fast.
Avoiding the getController method is good practice. Application logic tends to be better organized when using application events in place of logic that tightly couples controllers with each other.
Ext.define('UD.controller.c1', {
extend: 'Ext.app.Controller',
refs: [
{
selector: 'viewport',
ref: 'userview'
}
],
init: function() {
console.log("initincontroller!!");
this.control({
'viewport > panel': {
render: this.onPanelRendered
}
});
},
onPanelRendered:function(){
alert("rendered");
UD.getApplication().fireEvent('myevent');
}
});
Ext.define('UD.controller.c2', {
extend: 'Ext.app.Controller',
refs: [
{
selector: 'viewport',
ref: 'userview'
}
],
init: function() {
console.log("initincontroller!!");
UD.getApplication().on({
myevent : this.doSomething
});
},
doSomething:function(){
alert("controller2");
}
});