AngularJS: Resolving not in RouteProvider but in Controller? - angularjs

I saw some sample code here Delaying AngularJS route change until model loaded to prevent flicker
And straight away I though this was the right way to go, I need to have my controller LOAD only when a resolve is finished loading, normally most of the examples around tell you to put the code under resolve in the routeprovder as an inline function, but this sounds wrong. The controller needs it so why not have the controller implement the function to resolve. This sounded just what I was looking for ie. This seems to use the prototype pattern ??
function PhoneListCtrl($scope, phones) {
$scope.phones = phones;
$scope.orderProp = 'age';
}
PhoneListCtrl.resolve = {
phones: function(Phone, $q) {
// see: https://groups.google.com/forum/?fromgroups=#!topic/angular/DGf7yyD4Oc4
var deferred = $q.defer();
Phone.query(function(successData) {
deferred.resolve(successData);
}, function(errorData) {
deferred.reject(); // you could optionally pass error data here
});
return deferred.promise;
}
}
Problem is I have my controller like so
'use strict';
angular.module('TestApp')
.controller('ItemsCtrl', function ($scope) {
});
So how do I apply a new function on the controller when my controller is declared inside a module ?
What I really need is something like
TestCtrl.resolve = {
items: function( $q) {
..........
return deferred.promise;
}
}
This then would allow me to have in my routeprovider..
when('/items', {
templateUrl: 'views/items.html',
controller: 'TestCtrl',
resolve: 'TestCtrl.resolve'}). // Need to use ' around resolve?
But I am confused how I would get this to work ?
I would really love any feedback, I am at a loss.

Not possible to define like 'TestCtrl.resolve' if you want to use resolve with .controller syntax then you have to define inline within route provider . The advantage of inline resolve in routeprovider is that you can reuse controller easily but using same controller and changing the logic in resolve function

You can also use a service:
resolve: {data : function(service) {
return service.getData();
}}

Related

Does UI-Router Resolve Work with Angular 1.5 Components?

So, UI-router resolves were a thing of beauty in angular 1:
$stateProvider.state('myState', {
resolve:{
myVarFromResolve: function(){
return 'test';
}
}
})
controller: function($scope, myVar){
$scope.myVar= myVarFromResolve;
if(true){console.log($scope.myVar}; //logs 'test'
}
How do I do the same with an Angular 1.5. component (example below)?
export default function FooComponent() {
let fooComponent = {
restrict: 'E',
templateUrl: 'foo.html',
controller: controller,
controllerAs: 'foo',
bindToController: true,
};
return landingComponent;
}
And the resolve...
.state('fooState', {
parent: 'someParent',
url: '/fooUrl',
template: '<foo></foo>',
resolve:{
myVarFromResolve: function(){
return 'test';
}
}
})
I read a guide on how to do this, but I don't really understand it. Seems like the functionality isn't fully in place and that's a hack.
Looks like the answer is "Yes, kinda"...
I was given a work-around to this problem by a team member that works for angular-ui-router 0.2.18 (use npm list angular-ui-router --depth=0 to check your version quickly). It does work while using ES6 classes.
The work around is based on the fact that there is no (easy/known) way to get the variable passed into the controller, like you would in Angular 1 before component architecture introduced in 1.5. Instead, we create a service method that creates a promise (or returns an http call). Then you simply put the promise in the resolve. The promise method WILL return before the controller loads. So while you do not have a easy to use var, you have the data you need in a service.
resolve: {
preloadSomeVariableIntoAService: function (MyExampleService) {
return MyExampleService.presetSomeVariable();
}
//Note that 'preloadSomeVariableIntoAService' won't be used anywhere in our case
}
The service could be something like this:
export default class MyExampleService {
constructor( $q ) {
this.q = $q;
this.myVariableFromResolve = undefined;
}
presetStreamStatus(){
var deferred = this.q.defer();
return $http.get(someUrl).then(response) => {
this.myVariableFromResolve = response.data;
deferred.resolve(events);
});
return deferred;
};
};
You will then be able to get myVariableFromResolve from the service. I am not sure you have to use $q deferred, but it seems to be necessary since I am not simply returning the call but also setting the variable. Not sure. Hope this helps someone.

$stateProvider resolve dependency on controller not working

I am using ui-router and trying to use resolve with no success.
Here is my app.js state looks
.state('addInvestment', {
url: '/addUpdateInvestment',
templateUrl: 'js/angular/modules/investment/views/AddUpdateInvestment.html',
resolve: {
InvestmentTypes: ["investmentService", function (investmentService) {
console.log("Resolving dependency...");
return investmentService.getInvestmentTypes();
}]
}
})
It calls my service just fine and I know that service returns data just fine because I am using this service on several places in my application.
Here is my controller looks like:
angular.module('InvestmentModule')
.controller('AddUpdateInvestment',
['$scope', 'toaster', '$state', '$stateParams', '$wizard', 'investmentService', 'InvestmentTypes',
function ($scope, toaster, $state, $stateParams, $wizard, investmentService, InvestmentTypes) {
$scope.modalData = {};
$scope.modalData.investmentTypes = InvestmentTypes.items;
}]);
When I load page, I see the following in chrome:
You have to delete the comma.
Then, you have to load the controller after the resolve has done. Almost you can try to use that controller in your state, something like this.
.state('addInvestment', {
url:'/addUpdateInvestment',
controller: 'AddUpdateInvestment as aui',
templateUrl:'js/angular/modules/investment/views/AddUpdateInvestment.html',
resolve: {InvestmentTypes: ['investmentService', function(investmentService) {
console.log("Resolving dependency...");
return investmentService.getInvestmentTypes();
}]}, // this will crash if you dont specify other parameter to the state.
})
EDIT:
Check if this fix the problem
InvestmentTypes: ['investmentService', // use single quotes
EDIT 2:
It seems that the injection in the resolve is like this:
resolve: {
InvestmentTypes: function(investmentService) {
console.log("Resolving dependency...");
return investmentService.getInvestmentTypes();
}
}
Please, look at the wiki here
I think I found the problem.
In your resolve you have InvestmentTypes in quotation marks
resolve: {
'InvestmentTypes': ['investmentService', function(investmentService){
return investmentService.getInvestmentTypes();
}]
}
if you change it to be
resolve: {
InvestmentTypes: ['investmentService', function(investmentService){
return investmentService.getInvestmentTypes();
}]
}
It should work. I've forked your plunker, if you open up the dev console it should log it to the console
http://plnkr.co/edit/KOxgOqsGJcTD1URuHEOn?p=preview
Finally,
After so much of a struggle, I made it working. There was a problem in my service where after getting response, it was calling a method called handleResponse. But that method was a not a return type of factory.. I don't know how does it make any difference if I call it from resolve Vs call it as a regular service in controller. But it was for some reason breaking the resolve.
Here is my old service..
angular.module('InvestmentModule').factory('investmentService', ['$rootScope', '$http', '$q', function($rootScope, $http, $q){
return{
getInvestmentTypes:function()
{
return $http.get(this.webServiceUrl+'getAllInstitutionsForDisplay').then(handleResponse, handleError);
}
}
function handleSuccess(response)
{
return response.data;
}
function handleError( response ) {
// The API response from the server should be returned in a
// nomralized format. However, if the request was not handled by the
// server (or what not handles properly - ex. server error), then we
// may have to normalize it on our end, as best we can.
if (
! angular.isObject( response.data ) ||
! response.data.message
) {
return( $q.reject( "An unknown error occurred." ) );
}
// Otherwise, use expected error message.
return( $q.reject( response.data.message ) );
}
}]);
With the new code, I added the code in the handleSucess in the method itself as seen below..
getInvestmentTypes:function()
{
return $http.get(this.webServiceUrl+'getAllInstitutionsForDisplay').then(function(response){return response.data}, handleError);
}
I might have to do similar thing for the handleError as well I believe..
BUT it works now beautifully for me.
Only the problem with this approach is that it is breaking the DRY approach.. Any suggestion to improve it will be appreciated..

Angular - How to show modal reject reason in table?

I have small problem to solve.
I have modal controller rejectIssueModalCtrl.js
(function () {
'use strict';
function rejectIssueModalCtrl($modalInstance, issue, $rootScope) {
var self = this;
self.cancel = function () {
$modalInstance.dismiss('cancel');
};
self.reject = function ($rootScope) {
$modalInstance.close(self.reason);
console.log(self.reason);
};
$rootScope.reasono = self.reason;
}
rejectIssueModalCtrl.$inject = ['$modalInstance', 'issue', '$rootScope'];
angular
.module('app')
.controller('rejectIssueModalCtrl', rejectIssueModalCtrl);
})();
When I click the button I can open this modal and write a reason. I want to show this reject reason in table in other controller.
Here's my code from other controller issueDetailsCtrl.js
$scope.reasonoo = $rootScope.reasono;
function rejectIssue() {
var rejectModal = $modal.open({
templateUrl: '/App/Issue/rejectIssueModal',
controller: 'rejectIssueModalCtrl',
controllerAs: 'rejectModal',
size: 'lg',
resolve: {
issue: self.issueData
}
});
rejectModal.result.then(function (reason) {
issueSvc
.rejectIssue(self.issueData.id, reason)
.then(function (issueStatus) {
changeIssueStatus(issueStatus.code);
getIssue();
}, requestFailed);
});
};
and html code
<div>
<span class="right" ng-bind="$root.reasono"></span>
</div>
As you can see I tried to use $rootScope. I can console.log the reason but I cant make it to show in this html. Any help?
We're missing some context, but I believe this is your problem:
self.reject = function ($rootScope) {
$modalInstance.close(self.reason);
console.log(self.reason);
};
$rootScope.reasono = self.reason;
Assuming self.reason is bound to the input in your modal, it won't be defined outside of the reject callback - that's the nature of async. You're able to log to console because you're doing so within the callback.
Define $rootScope.reasono inside of the callback like so:
self.reject = function () {
$modalInstance.close(self.reason);
console.log(self.reason);
$rootScope.reasono = self.reason;
};
Edited to show that $rootScope should be removed as a named parameter in the reject function definition.
Using root scope is not recommended. For this reason it is recommended to create a service for intercommuncation with variable to store reject reason, then inject this service for each controller - that way you will be able to read/write reason from different controllers.

Angular - The easiest way to populate $scope

I am currently populating $scope arrays like this:
$scope.categories = [];
$scope.getCategories = function() {
ItemService.getCategories().success(function(categories) {
$scope.categories = categories;
});
}
$scope.getCategories();
Is this really the easiest way to do it?
Do you actually need a getCategories function ? If this is the only place it's called, then you can just remove and leave the service call in directly.
Otherwise your code is as short as it gets for using promises I think, if you're using v1.2. Before 1.2, angular did have automatic promise unwrapping. That is, instead of
//$scope.getCategories = function() {
ItemService.getCategories().success(function(categories) {
$scope.categories = categories;
});
//}
You could do this instead, which seems much more elegant:
//$scope.getCategories = function() {
$scope.categories = ItemService.getCategories();
//}
Issues with this mean it's recently been removed though - https://github.com/angular/angular.js/issues/4158 . I think it's possible an alternative will be added in the future if there's a good way to do it though.
You could simply use the resolve property of your router (assuming you're using a router) to inject the results from ItemService.getCategories() into your controller. resolve properties block the route from completing until they're fully resolved--it unwraps promises automatically. The code would look something like:
angular.module('MyModule').config(function ($routeProvider) {
$routeProvider.when('/mypage', {
templateUrl: '/mypage.html',
controller: 'MyController',
resolve: {
categories: function (ItemService) { return ItemService.getCategories(); }
}
});
});
angular.module('MyModule').controller('MyController', function (categories, $scope) {
$scope.categories = categories;
});

asynchronous loading controller's code

how do you bootstrap a controller that loaded asynchronously via require.js?
if I have something like that:
$routeProvider.when('/',
{
templateUrl:'view1.html',
controller:'ctrl',
resolve:{
load:function($q){
var dfrd = $q.defer();
require(['view1-script'],function(){
dfrd.resolve();
})
return dfrd.promise;
}
}
})
why angular still won't find the controller? I am resolving the route after it loads the script
check out this plunkr
try calling $controllerProvider.register to create your controller. I would also call $apply() on the $rootScope after resolving the deferred because without it, the view does not seem to appear:
load: function($q, $rootScope){
var dfrd = $q.defer();
require(['view1'],function(){
dfrd.resolve();
$rootScope.$apply();
})
return dfrd.promise;
}
http://plnkr.co/edit/fe2Q3BhxPYnPmeiOORHP
in addition, here is a good post: http://weblogs.asp.net/dwahlin/archive/2013/05/22/dynamically-loading-controllers-and-views-with-angularjs-and-requirejs.aspx
It's been 3 years, but just in case anyone still interested, a few months ago I wrote a post about a similar technique to do it.
The most important part is that second parameter of the method $routeProvider.when(route, ctrl) method can handle promises, so you can simply emulate it:
function controllerFactory(ctrl) {
return {
then: function (done) {
var self = this;
require(['./controller/' + ctrl], function (ctrl) {
self.controller = ctrl;
self.resolve = ctrl.resolve;
self.templateUrl = ctrl.templateUrl;
done();
});
}
};
}
And you can end up writing your route definition like this:
$routeProvider.
when('/some/route', controllerFactory('some/route')).
when('/other/route', controllerFactory('other/route'))

Resources