jasmine $timeout triggers ui-router resolve elsewhere - angularjs

I have a jasmine test for a directive that flushes a $timeout in order to test certain code within the directive. At another point in the application, I have a ui-router handling navigation with the following resolve
$stateProvider
.state('home', {
url: '/home',
component: 'home',
resolve: {
myservice: "myservice",
myItems: function(myservice) {
return myservice.resource.query().$promise;
}
}
})
The directive is in the same angular module as this home component (the directive is nested inside of the component).
Whenever the test runs for the directive and $timeout.flush() is called, I get an 'error: unexpected request' with the url of the query. This is happening even though the directive and that component shouldn't be associated. A quick fix is to just add in an $httpBackend to fix it, but this shouldn't be happening, and as more things are added I think the problem will be replicated.
I have confirmed that a) it is that resolve function that is triggering the request, b) I have checked the $state in the directive tests but it returns an empty string so I don't think its trying to set up the home state c) There is no code in the directive test referencing this component or anything like that
If it helps, the setup code in the test is as follows:
scope = $rootScope.$new();
var element = $compile('..../*doesnt include home component at all*/......')(scope);
scope.$digest();
$timeout.flush();

Okay..... after reading the following https://github.com/angular-ui/ui-router/issues/212 for way too long, the appropriate solution is:
beforeEach(module(function($urlRouterProvider) {
$urlRouterProvider.deferIntercept();
}))
The aforementioned resolve that was the problem is called in the state that $urlRouterProvider.otherwise() points to. By deferring that we prevent the home state from being realized and that resolve from triggering the resource call

Related

Why getting provider error when trying to inject resolved property in controller using ui-router?

I am unable to inject resolve property of ui-routing in controller.
It is giving
Error: $injector:unpr
Unknown Provider
When I'm using controller property in state definition object as following
.state('widget', {
url: '/widgets',
template: '<h1>{{name}}</h1>',
controller: function(widget, $scope) {
$scope.name = widget.name;
},
resolve: {
// standard resolve value promise definition
widget: function() {
return {
name: 'myWidget'
};
},
// resolve promise injects sibling promise
features: function(widget) {
return ['featureA', 'featureB'].map(function(feature) {
return widget.name+':'+feature;
});
}
}
});
Then it is working fine and I'm able to get the widget in controller and able to use in html.
Please see the fiddle for code.
http://jsfiddle.net/sunilmadaan07/ugsx6c1w/8/
Might be I'm making a silly mistake.
Before posting this question I have tried returning with simple object, promise object to the property.
Thanks In Advance.
You can not get resolved data in the directive with the code you did. Basically, you are trying to implement component based structure with an older version of angular 1.3.x.
You have two options to achieve this.
Create route controller then you can access resolve to the controller as local dependency then use that dependency as binding to the directive.
Here is example - http://plnkr.co/edit/TOPMLUXc7GhXTeYL0IFj?p=preview
Upgrade angular version to 1.5.x and use "ui-router-route-to-components": "^0.1.0"
Here working example of your code - http://jsfiddle.net/ugsx6c1w/25/
In order for the controller to be able to use resolvers, it should be route component (only in UI Router 1.x) or route controller.
widget and features are local dependencies, they aren't registered in the injector and aren't available anywhere in the application except route controller/component.
As explained here, resolvers can be passed to nested components in UI Router 0.3.x and injected directly to route components in 1.x.

What is the best way to resolve data before loading view and controller in Angular JS?

I've a page with Navbar and Sidebar that remains common across most of the pages and hence I load all data related to LoggedInUser in NavbarController.
This data ($scope.loggedInUser) is used in other Controllers (child controller) as well. Since call to get User data is async, many times, child controller tries to access data before it is returned from the server.
What is the best way to ensure, all promises of parent controller are resolved before child controller starts it's work?
You should use resolve from $stateProvider. This function ensures that all the promises are resolved before loading a new state.
A resolve is a property you can attach to a route in both ngRoute and the more robust UI router. A resolve contains one or more promises that must resolve successfully before the route will change. This means you can wait for data to become available before showing a view, and simplify the initialization of the model inside a controller because the initial data is given to the controller instead of the controller needing to go out and fetch the data.
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}
}
})
in controller
app.controller("newsController", function (message) {
$scope.message = message;
});
Hope this will help you

Angular Ui-router Resolve concept

I was reading the docs of ui-router but I couldn't grasp the concept of resolves for controllers in each state. I am not able to figure out where should we use resolve and why the controller attached to a state is not enough (as we can inject any dependencies in it we want) ?
I've tried going through docs and other tutorials several times but its quite confusing , Can someone please explain it with its real life application?
Imagine you want to create a modal and pass some data to it. I'm using the angular-ui-bootstrap modals for this example.
var openExampleModal = function () {
var modalInstance = $modal.open({
templateUrl: "Modal.html",
controller: "ModalController",
size: "lg"
});
return modalInstance.result;
};
Now if you want to pass some data to this modal on initialization, you can either save it in your $rootScope or some data service, or you can use resolve to inject it into your controller directly without having to use anything else.
var openExampleModal = function (myData) {
var modalInstance = $modal.open({
templateUrl: "Modal.html",
controller: "ModalController",
size: "lg",
resolve: {
sampleData: function () {
return myData;
}
}
});
return modalInstance.result;
};
and in your controller you would have:
MyController.$inject = ["sampleData"];
function Mycontroller(sampleData) {
//You can access the data you passed on via sampleData variable now.
};
Resolve is used to inject your own custom objects into the controller, not for injecting dependencies.
A resolve is simply a value that is passed to the controller upon instantiation (which are used like an injected value). The neat thing about them is that if the value returned is a promise, the view/controller won't load until the promise has resolved.
The way you use them is by adding a resolve key to your route state, and returning the object you want injected into your controller (also naming it). For example:
.state('example', {
url: '/page',
templateUrl: 'sometemplate.html',
controller: 'SomeCtrl',
resolve: {
injectionName: function(){
// return a value or promise here to be injected as injectionName into your controller
}
}
});
Then inside your controller you simply add the resolve name to the controller injected values:
.controller('SomeCtrl', function($scope, injectionName){
// do stuff with injectionName
});
Just note that if you do return a promise, the value that is injected is the result of the promise (not the promise itself). Also note that if the promise errors the view/controller will not load, and an error will be thrown. As #koox00 commented, this error will fail silently unless $stateChangeErrorError is handled (usually in your apps primary run() function).
So why would you use this? Well if not inferred from above, you do this usually when you want your view/controller to wait until some async process has completed before loading a particular state. This saves you from creating loaders or loading processes for every single view/controller, moving it to a simple definition of what needs to be loaded.
As said by Jean-Philippe you can use resolve if you want to load some data before switching to a certain state. Resolve waits and blocks until the data is arrived and only then the state transition is done.
It is an highly discussed topic whether using a resolve or loading the data on the fly within the controller. I would say: It depends on your use case :)
Further info from supercool todd motto: https://toddmotto.com/resolve-promises-in-angular-routes/

How to get the current route's controller (or scope)?

I have a controller that needs a thing provided by a route resolve function:
$routeProvider.when('/some/url', {
controller: MyController,
controllerAs: 'myCtrl',
resolve: {
theAnswer: ['deepThought', function(deepThought) {
return deepThought.computeTheAnswerAndReturnAPromise();
}]
}
});
var MyController = ['$route', function($route) {
this.theAnswer = $route.current.theAnswer;
}];
Now I want to do an end-to-end test, checking that the route matches and that parameters are propagated properly:
// ...set up the routes...
$location.path('/some/url');
$rootScope.$digest();
var ctrl = ???;
expect(ctrl.aThing).toBe(42);
In the non-test setup, I can put in a log statement and see that the controller is being created successfully and gets the correct data injected. The only problem is: how to get hold of the controller in the test?
There is $route.current.controller, but it contains the controller's constructor function and not the controller instance.
The documentation promises a $route.current.locals.$scope, from which I could get myCtrl, but the $scope property doesn't actually exist unless we also use ngView (it gets set here).
The controller isn't registered with any module, so I can't use $provide to intercept its creation and stash the controller somewhere.
Found it, thanks to #PSL's comment. The thing that actually constructs the controller is the ngView link function. We can fake that easily enough:
var ctrl = $controller(MyController, $route.current.locals);

Wait until scope variable is loaded before using it in the view in angular.js

I've seen this and this but it seems like there might be a simpler way.
In my view I have several menu options that are controlled through permissioning - i.e., not everyone can see a "Dashboard" view. So in my menu option in my view I have something like the following:
<li ng-show="validatePermission('Dashboard')">Dashboard</li>
In my controller I have a validatePermission method defined where it is looking at the permissions of the current user. For example:
$scope.validatePermission = function(objectName) {
if $scope.allPermissions......
Also in my controller I'm loading those permissions via an $http call:
$http.get('permissions/' + userid + '.json').success(function(data) {
$scope.allPermissions = data;....
The issue is that $scope.allPermissions doesn't get loaded before the view makes the call to validatePermission. How can I wait for allPermissions to be loaded before the view renders?
You ask:
How can I wait for allPermissions to be loaded before the view renders?
To prevent the entire view from rendering, you must use resolve. You don't have to use the promise library though, since $http returns a promise:
var app = angular.module('app');
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl : 'template.html',
controller : 'MyCtrl',
resolve : MyCtrl.resolve
});
});
function MyCtrl ($scope, myHttpResponse) {
// controller logic
}
MyCtrl.resolve = {
myHttpResponse : function($http) {
return $http({
method: 'GET',
url: 'http://example.com'
})
.success(function(data, status) {
// Probably no need to do anything here.
})
.error(function(data, status){
// Maybe add an error message to a service here.
// In this case your $http promise was rejected automatically and the view won't render.
});
}
}
But if you simply want to hide the dashboard <li>, then do as Joe Gauterin suggested. Here's a very simple example plunkr if you need it.
Have the validatedPermission function return false when allPermissions hasn't been loaded. That way the element with your ng-show won't be displayed until allPermissions has been loaded.
Alternatively, put an ng-show="allPermissions" on the enclosing <ul> or <ol>.
You can also specify on your routecontroller a resolve object that will wait for that object to resolve prior to rendering that route.
From the angular docs: https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
resolve - {Object.=} - An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $routeChangeSuccess event is fired. The map object is:
key – {string}: a name of a dependency to be injected into the controller.
factory - {string|function}: If string then it is an alias for a service. Otherwise if function, then it is injected and the return value is treated as the dependency. If the result is a promise, it is resolved before its value is injected into the controller.
A google group reference as well: https://groups.google.com/forum/#!topic/angular/QtO8QoxSjYw
I encountered an similar situation, you might also want to take a quick look at
http://docs.angularjs.org/api/ng/directive/ngCloak
if you're still seeing a "flicker" effect.
As per the angularjs documentation:
The ngCloak directive is used to prevent the Angular html template from being briefly displayed by the browser in its raw (uncompiled) form while your application is loading. Use this directive to avoid the undesirable flicker effect caused by the html template display.
Wrapping the code in ng-if fixed the issue for me:
<div ng-if="dependentObject">
<!-- code for dependentObject goes here -->
</div>

Resources