I am attempting to lazy load a controller and template in my UI-Router router.js file, but am having difficulty with the template.
The controller loads properly, but after that is loaded, we must load the template and this is where things go wrong.
After ocLazyLoad loads the controller, we resolve an Angular promise which is also included in the templateProvider. The issue is instead of returning the promise (templateDeferred.promise) after the file is done loading, the promise is returned as an object.
.state('log_in', {
url: '/log-in',
controller: 'controllerJsFile',
templateProvider: function($q, $http) {
var templateDeferred = $q.defer();
lazyDeferred.promise.then(function(templateUrl) {
$http.get(templateUrl)
.success(function(data, status, headers, config) {
templateDeferred.resolve(data);
}).
error(function(data, status, headers, config) {
templateDeferred.resolve(data);
});
});
return templateDeferred.promise;
},
resolve: {
load: function($templateCache, $ocLazyLoad, $q) {
lazyDeferred = $q.defer();
var lazyLoader = $ocLazyLoad.load ({
files: ['src/controllerJsFile']
}).then(function() {
return lazyDeferred.resolve('src/htmlTemplateFile');
});
return lazyLoader;
}
},
data: {
public: true
}
})
Ok, thanks for the responses, but I have figured it out.
.state('log_in', {
url: '/log-in',
controller: 'controllerJsFile',
templateProvider: function() { return lazyDeferred.promise; },
resolve: {
load: function($templateCache, $ocLazyLoad, $q, $http) {
lazyDeferred = $q.defer();
return $ocLazyLoad.load ({
name: 'app.logIn',
files: ['src/controllerJsFile.js']
}).then(function() {
return $http.get('src/htmlTemplateFile.tpl.html')
.success(function(data, status, headers, config) {
return lazyDeferred.resolve(data);
}).
error(function(data, status, headers, config) {
return lazyDeferred.resolve(data);
});
});
}
},
data: {
public: true
}
})
So, after some more reading, I realized I had an issue with my promises. We create one called lazyDeferred which is the one to be returned to templateProvider and is declared as a global variable. templateProvider waits for the promise to be fulfilled.
After we load our controller, we create an XHR/ $http request to retrieve the template file. $http.get is a promise so we can return that, $ocLazyLoad.load also is a promise so we can return that as well. Finally, we just need to resolve the lazyDeferred one and that I think balloons through the promises and resolves all of them.
I apologize if this was not very clear, I'm not 100% sure of how this works.
In case you'd like to lazily load the controller, I would suggest follow these detailed answers:
requirejs with angular - not resolving controller dependency with nested route
angular-ui-router with requirejs, lazy loading of controller
In case we need to load dynamically the HTML template, it is much more easier. There is an example from this Q & A
Trying to Dynamically set a templateUrl in controller based on constant
(the working plunker)
$stateProvider
.state('home', {
url: '/home',
//templateUrl: 'index5templateA.html', (THIS WORKS)
templateProvider: function(CONFIG, $http, $templateCache) {
console.log('in templateUrl ' + CONFIG.codeCampType);
var templateName = 'index5templateB.html';
if (CONFIG.codeCampType === "svcc") {
templateName = 'index5templateA.html';
}
var tpl = $templateCache.get(templateName);
if(tpl){
return tpl;
}
return $http
.get(templateName)
.then(function(response){
tpl = response.data
$templateCache.put(templateName, tpl);
return tpl;
});
},
You can check these as well:
Angular UI Router: decide child state template on the basis of parent resolved object
Angular and UI-Router, how to set a dynamic templateUrl
Related
Very simply, after an API call, depending on the return value, how is the appropriate view loaded? Consider having
search.html
views/found.html
views/notfound.html
Search's controller makes an AJAX call to a service and gets a good or bad result. Now I want the appropriate view to load, without user having to click. I just can't figure out how to do this and have looked at scores of routing/view examples. I'm using HTML5 mode.
app.config(['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
$routeProvider
.when('/', {
templateUrl: 'search.html',
controller: 'searchCtrl'
})
.when('found', {
templateUrl: 'views/found.html',
controller: 'foundCtrl'
})
.when('notFound', {
templateUrl: 'views/notFound.html',
controller: 'notFoundCtrl'
})
.otherwise({
templateUrl: 'search.html',
controller: 'searchCtrl'
});
$locationProvider.html5Mode({
enabled: true,
requiredBase: true
});
And in the controller ..
$scope.post = function (requestType, requestData) {
var uri = 'Search/' + requestType;
$http.post(uri, requestData)
.success(function (response) {
$scope.searchResult.ID = response.ID;
$scope.searchResult.Value = response.Value;
// how is the view/route loaded without a user click?
'found';
return true;
}).error(function (error) {
// how is the view/route loaded without a user click?
'notFound';
return false;
});
I'm just lost after getting back the response on how to invoke a view within the template.
Since you are using ngRoute use $location.path() instead of $state.go(). The $location.path() method accepts a url specified in route configuration. E.g.:
$location.path('/found');
Say your controller is AppController, then the complete code will look something like:
angular.module('app', ['ngRoute'])
.controller('AppController', function ($location, $http) {
$scope.post = function (requestType, requestData) {
var uri = 'Search/' + requestType;
$http.post(uri, requestData)
.success(function (response) {
$scope.searchResult.ID = response.ID;
$scope.searchResult.Value = response.Value;
// how is the view/route loaded without a user click?
$location.path('/found');
}).error(function (error) {
// how is the view/route loaded without a user click?
$location.path('/notFound');
});
});
Refer https://docs.angularjs.org/api/ng/service/$location for api documentation of $location.path
I am using $http in angular for ajax calls and using ui.router for routing.
Routes
.state("/dashboard.inactive", {
url: "/inactive",
templateUrl: "angular/templates/dashboard/inactive.html",
controller: "dashboardCtrl"
})
.state("/dashboard.drafts", {
url: "/drafts",
templateUrl: "angular/templates/dashboard/drafts.html",
controller: "dashboardCtrl"
});
So the below code works if it is for a single URL.
Controller
app.controller('dashboardCtrl', function ($scope, DashboardFactory){
DashboardFactory.listings(function(DashboardFactory) {
$scope.results = DashboardFactory;
});
});
Below factory is fetching only from drafts.json resource. So when the URL changes to inactive I want it to fetch from inactive.json and active.json respectively.
Factory
app.factory('DashboardFactory', function($http){
return {
listings: function(callback){
$http.get('drafts.json').success(callback);
}
};
});
In short I need to send requests to any one of the below 3 URLs based on the URL
1) '/drafts.json'
2) '/inactive.json'
3) '/active.json'
I can create a different controllers for each active, inactive and drafts and make it fetch as expected. But is there any better way to do this??
You could use the $state service of ui route in order to tell which state your are in.
Just inject $state to your service and then use $state.current in order to access the current state config.
app.factory('DashboardFactory',
function($http, $state){
return {
listings: function(callback){
var currentView = $state.current.url.replace('/', '');
$http.get(currentView + '.json').success(callback);
}
};
});
A better solution would be to either use the params property of the state config or add some custom property like:
.state("/dashboard.inactive", {
url: "/inactive",
templateUrl: "angular/templates/dashboard/inactive.html",
controller: "dashboardCtrl",
params: {
json: 'inactive.json'
}
})
.state("/dashboard.drafts", {
url: "/drafts",
templateUrl: "angular/templates/dashboard/drafts.html",
controller: "dashboardCtrl",
params: {
json: 'drafts.json'
}
});
It is described in the documentation.
I need to pass a route parameter to the server responding with a template, so I'm trying to use a templateProvider per several articles/other stack overflow docs.
I'm not getting any javascript errors, but the following templateProvider method is never even executed. When the templateUrl property is not commented out, this route works fine.
$stateProvider
.state('org.contacts.add', {
url: '/add',
views: {
'org#': {
// templateUrl: '/templates/issues/add',
controller: 'ContactsAddController',
templateProvider: function($route, $templateCache, $http) {
var url = '/templates/' + $route.current.params.org + '/contacts/add';
$http.get(url, {cache: $templateCache}).then(function(html){
return html;
});
}]
}
}
})
After some experimentation, it seems the $route was causing trouble. Taking that out and using $stateParams at least fires this code/controller.
However, while I see the ajax call firing and the proper html response, it's never loaded to the view.
templateProvider: function ($stateParams, $http, $templateCache) {
var url = '/templates/contacts/add';
$http.get(url, {cache: $templateCache}).then(function(html){
return html;
});
}
I'm guessing you need to return a $promise for this to work. Check out the example used on the UI-Router wiki:
$stateProvider.state('contacts', {
templateProvider: function ($timeout, $stateParams) {
return $timeout(function () {
return '<h1>' + $stateParams.contactId + '</h1>'
}, 100);
}
})
Notice the line that begins with return $timeout(.... Have you tried returning the $promise that is created by doing $http.get()?
I am continuing to have issues where my templates and directives are throwing errors because they are trying to be $compiled before the data is actually set. This is because it takes time for the API response to get back.
Therefore, I am trying to convert my API call to work in the resolve property of my route, however I cannot figure out how to do it correctly. Here is what I have:
My State Provider w/ resolve property
$stateProvider
.state('dashboard', {
url: '/',
controller: 'LandingController',
resolve: {
data: ['API', function(API){
return API.Backups.getAll(function (data) {
return data.result;
});
}]
}
})
My controller
app.controller('LandingController', ['$scope', 'API', function ($scope, API, data) {
$scope.data = data;
......
I am using an Angular service that provides a $resource in order to get the API data, however something is not working still because my data parameter in the controller is still undefined.
I figured out what I needed. I just needed to return the $resource.$promise instead of just the $resource. After doing that, everything worked great!
Solution
$stateProvider
.state('dashboard', {
url: '/',
controller: 'LandingController',
resolve: {
res: ['API', function(API){
return API.Backups.getAll(function (data) {
return data.result;
}).$promise;
}]
}
})
Without the code for API.Backups.getAll() I can't tell, but the resolve function needs to return a "promise" which can be provided by the Angular $q:
resolve: ['$q', 'API', function($q, API) {
// 1. $q provides the deferred which satisfies the "promise" API
var deferred = $q.defer();
// 2. call your API
API.Backups.getAll(function (data) {
// 4. resolve the promise with data that was returned
deferred.resolve(data.result);
});
// 3. return the promise
return deferred.promise;
}]
The numbered comments show the order things will occur. Angular will not instantiate your controller until resolve() is called on the promise (deferred).
see: https://docs.angularjs.org/api/ng/service/$q
I have an app with a service which wraps my API calls:
var ConcernService = {
...
get: function (items_url, objId) {
var defer = $q.defer();
$http({method: 'GET',
url: api_url + items_url + objId}).
success(function (data, status, headers, config) {
defer.resolve(data);
}).error(function (data, status, headers, config) {
console.log('ConcernService.get status',status);
defer.reject(status);
});
return defer.promise;
},
and I'm using UI-Router to transition between states:
concernsApp
.config( function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/404/");
$stateProvider.state('project', {
url: '/project/:projectId/',
resolve: {
project: function ($stateParams, ConcernService) {
return ConcernService.get('projects/', $stateParams.projectId);
},
},
views: {
...
}
});
I'm moving from using the normal AngularJS router and I'm having difficulty understanding how to implement 404s. I can see the ConcernService throwing the console.log status as rejected, but how do I catch this in the state router?
The otherwise() rule is only invoked when no other route matches. What you really want is to intercept the $stateChangeError event, which is what gets fired when something goes wrong in a state transition (for example, a resolve failing). You can read more about that in the state change event docs.
The simplest implementation for what you're trying to do would be something like this:
$rootScope.$on('$stateChangeError', function(event) {
$state.go('404');
});
Also, since $http itself is built on promises (which resolve resolves), your ConcernService method can be simplified down to a one-liner (I realize you expanded it for debugging purposes, but you could have just as easily chained it, just FYI):
var ConcernService = {
get: function (items_url, objId) {
return $http.get(api_url + items_url + objId);
}
}
I differ between two 404 states:
Server:
show 404 page depending on server response HTTP Code 404
important to define no URL, so that user stays on URL where the error happened
Client:
URL is not found by angular ui router (none of defined URLs)
Code for Angular UI-Router state:
$stateProvider
.state('404server', {
templateUrl: '/views/layouts/404.html'
})
.state('404client', {
url: '*path',
templateUrl: '/views/layouts/404.html'
});
Code in $httpProvider interceptor:
if(response.status === 404) {
$injector.get('$state').go('404server');
}
And why I used $injector instead of $state is explained here.
You can also try something like this and see if it works for you. You may need to adjust to your needs:
.state('otherwise', {
abstract: true,
templateUrl: 'views/404.html'
})
.state('otherwise.404', {
url: '*path',
templateUrl: 'views/404.html'
})
The $urlRouterProvider only works like a $watch to $location and if the actual URL matches one of the rules defined in the .config() function then it will redirect to the specified route.
Here's what I recommend, define "/404/" as a state:
$stateProvider.state('404', {
url:'/404/',
views:{
...
}
});
And inside the reject() function move to 404 state
if(status == '404'){
$state.transitionTo('404');
}
You will have to add ui-router as dependency of the project module and use the $state provider in your controller in order to be able to use $state.transitionTo()
Here's some info: https://github.com/angular-ui/ui-router/wiki/Quick-Reference#statetransitiontoto-toparams--options
I managed to handle 404 without using $urlRoutProvider since I'm only using states by testing $state.transistion:
angular.module("app", []).run(["$state", "$rootScope", function($state, $rootScope) => {
$rootScope.$on("$locationChangeSuccess", function() {
if (!$state.transition) {
$state.go("404");
}
});
}]);
$urlRouterProvider.otherwise('/page-not-found');
.state('error', {
url: "/page-not-found",
templateUrl: "templates/error.html",
controller: "errorController"
})
Will handle your page not found problem.
If you want to raise 404 found purposefully use the state or url. We have created a separate controller just if you want to perform any operations.