Prevent all $state transitions until promise is resolved - angularjs

I am using ui-router.
I want to be able to block any state transitions until a promise is resolved.
There's are multiple states, so setting the same resolve property on all is not a good idea.
The promise needs to be resolved inside the Angular app, so bootstrapping the app after an external promise is resolved won't work.
My current solution relies on having a $stateChangeStart listener that calls event.preventDefault(); and which removes itself after the promise is resolved. There are many complications with this solution besides the fact that its intention isn't clear unless well commented.
So, is there a better solution to block all state transitions until everything's cool?

Create one abstract parent state, make the rest children of that state. Use the resolve object on the parent state so it's resulting dependency will be available to all child states.
See: https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#abstract-states
An abstract state can have child states but can not get activated itself. An 'abstract' state is simply a state that can't be transitioned to. It is activated implicitly when one of its descendants are activated.
Some examples of how you might use an abstract state are:
To provide resolved dependencies via resolve for use by child states.

I think you want to use $urlRouterProvider.deferIntercept()
See here: http://angular-ui.github.io/ui-router/site/#/api/ui.router.router.$urlRouterProvider
app.config(function ($urlRouterProvider) {
$urlRouterProvider.deferIntercept();
};
app.run(function($urlRouter, myservice) {
myservice.promise.then(function() {
$urlRouter.listen();
$urlRouter.sync(); // not sure if this is necessary
});
});

Related

Refer to different state

When my is initiated I get directed to this state:
$stateProvider.state('dashboard.tasks.overview')
I have a link that I want to use to go into a new state
<div ui-sref="responsive.details({id:'6710cc8c-e0ca-e811-80fd-00155d168404'})">click</div>
And a routeconfig with $stateProvider.state('responsive.details')
When I click the link I get:
Possibly unhandled rejection: {"$id":2,"type":4,"message":"This transition is invalid","detail":"Could not resolve 'responsive.details' from state 'dashboard.tasks.overview'"}
So when I click the link it triest to find the responsive.details child route in the dashboard.tasks.overview. Is it possible to direct a ui-sref to a new route instead of a child route in a parent?
In ui-router, states are built up in a hierarchy. This means that the responsive.details state is a child of the responsive state. When you navigate to some state, any parent states of that state must also be loaded.
In your example, you never mentioned creating a responsive state.
Add some sort of $stateProvider.state('responsive', {...}); call.
Alternatively, just rename the responsive.details state to responsiveDetails, as a test.
See the ui-router documentation for states.

How trigger changes in controller from a service WITHOUT employing $watch, $broadcast, $emit, etc

I have a bunch of hierarchically arranged components, namely:
partner component, that works with partner organizations, knows how to update or remove them, etc.;
leader component, that works with leader organizations, knows how to deal with leader organizations;
list component, than displays to the user both partner and leader organizations;
a service - when partner or leader components removes an organization, info on this organization is passed to the list component so that these changes are reflected in front immediately.
I cannot provide code samples on this, it's too big, moreover my question mostly requires a conceptual advice rather than code issues.
Right at the moment it perfectly works - components are doing their job sending data to the service:
IndexCollection.setIndexes(
vm.leaderIndex, vm.partnerIndex, 'added_l', response.id
);
the service does it's job and pushed changes to a variable, which I $watch in the list component to trigger respective actions like this:
$scope.$watch(() => IndexCollection.indexes, function() {
let indexes = IndexCollection.indexes;
switch (indexes.message) {
case 'deleted_p':
removePartner(indexes);
break;
case 'deleted_l':
removeLeader(indexes);
break;
case 'added_l':
addLeader(indexes);
break;
}
});
My question is of a more theoretical essence. Is there a possibility to trigger real-time actions from service in the list component without using$watch, $emit, $broadcast and other standard tools we usually use in this regard?
Can I somehow achieve the same result by means of using callbacks? I mean, when a change in service occurs, it triggers immediate action in the respective controller?
While using $watch may solve the problem, it is not the most efficient solution.
You might want to change the way you update and retrieve the data of your service.
The component controllers should manipulate the data stored in your service with functions in your service based on actions/events triggered from your component and you inject the service in the component.
MyDataService.getIndexCollection() {}
MyDataService.putIndexCollection() {}
Then you pass the data down to all your directives and components via require or bindings for components or isolated scopes for your directives.
For example once the partner components edits the data on the service you fetch the data again from your service and the updated data will be passed to your list component and update the view via $apply() if needed.

Usecase for $emit rather than directly accessing parent scope

I'm struggling to understand why you'd need to use $emit rather than using the controller as syntax and directly accessing & updating data on the parent scope. What are the use cases?
It's obviously more efficient to call a method on a parent scope directly, rather than using $emit (or $broadcast) to dispatch an event. But there are some valid reasons to do it.
Here's some reasons that come to mind:
You want to emit an event that more than one application component can receive/process.
You want to de-couple your components so that in the future the message may be processed by a different controller (or maybe a service or a directive, etc.).
You want to emit an event that a service/factory can consume (these are not associated with any view/$scope, but you can inject the $rootScope into them).

ui-router: Do controllers ever get destroyed after transitions?

I am using ui-router and am trying to detect when a controller belonging to a view that's being transitioned away from gets destroyed.
So I currently have a destroy listener like so:
$scope.$on('destroy', function(){
mySpecialFunction();
});
However, when a state change occurs to a different view\controller, this destroy event never fires.
The state I am transitioning to, is a sibling state so I'm going from myParent.childA to myParent.childB (where childA has the destroy listener added).
If I was instead going from myParent.childA to myParent.childA.child1 then this would make sense since childA still exists in the hierarchy.
Can someone help me understand why the scope still exists in this scenario please?
The event name you want is "$destroy" not "destroy"
Try
$scope.$on('$destroy', function(){
mySpecialFunction();
});
This should get triggered under the conditions mentioned in question

AngularJS scope not updating

After executing get or put in indexeddb, the callback is updating the scope.
The problem is, no update is being triggered in the ui!
A common solution is to use apply or digest but that is wrong, you should NOT use those operations. Angular should update it automatically.
Now my guess, after reading some stuff, is that the callback is being executed in a different context, outside of the scope.
My question is basically: how can I execute an indexeddb callback in an angularjs scope context?
EDIT:
A rough look on how its built:
GetObjectStoreData(iDb, objectStore, function (res) {
$scope.result = res;
}
The call is made from inside the scope so the scope is used in the callback. The callback parameter is the contents of the object store
You asserted that you should not have to use Scope.$apply because AngularJS should do it for you.
This is true only for callbacks managed by Angular. For example, when you use $timeout in place of window.setTimeout, angular wraps your provided callback in a call to Scope.$apply, causing a digest cycle to run once your callback completes. If you interact directly with browser APIs rather than calling through AngularJS, it is your responsibility to call Scope.$apply at the appropriate time.
If you don't want to manage the scope directly, you could instead use a wrapper library like angular-indexedDB, which (amongst other things) handles the scope notifications when callbacks occur.

Resources