Angular ngRoute not working on reload with ngInclude - angularjs

In case anyone else runs into the same issue. While trivial to some, it was not trivial for me.
The problem:
When the page loads ngRoute fails to load the shell (landing page) template when using ng-view inside ng-include.
However if we start navigating around the pages including navigating back to landing page then the template loads just fine. It is as if the first time the page loads it completely skips loading the landing template.
The setup:
Somewhere at the bottom of index.html's body:
<script src="angular-route.js"></script>
Somewhere in index.html template
<div ng-include="'root-shell.html'"></div>
Somewhere in root-shell.html template
<div ng-view></div>
The JS:
(function (/* IIFE enclosed code*/) {
angular('myApp', [$routeProvider]);
angular('myApp').config(function($routeProvider){
$routeProvider
.when('/', {
templateUrl: 'root-shell.html',
controller: 'RootShellController', // optional
controllerAs: 'rootShellCtrl' // optional
}).
.when('/about', {
templateUrl: 'about.html',
controller: 'AboutController', // optional
controllerAs: 'aboutCtrl' // optional
}).
otherwise({
redirectTo: '/'
});
});
})(/* IIFE invoke*/);

Why the problem?
Because if ng-view instantiation is delayed (through ng-include) then the $route instantiation is delayed as well. This means that $route will miss the location change event.
The fix:
Force a reload of the $route service.
angular('myApp').run(['$route', function($route) {
$route.reload();
}]);
Why does it work?
Because of angular's order of execution:
app config
app run
directive setup
directive compile
app controller
directive link
** Data resolve called **
new route's controller
and the run block:
Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kickstart the application. It is executed after all of the services have been configured and the injector has been created. Run blocks typically contain code which is hard to unit-test, and for this reason should be declared in isolated modules, so that they can be ignored in the unit-tests.
Disclaimer:
I am fairly new to Angular, therefore if I've misunderstood and/or misrepresented a concept then by all means chime in.

Related

Angular templateUrl not redirecting to MVC controller

So I am having an issue in setting up my angular routes.
Moving straight to the point, my angular routes defined don't hit my mvc controller and thus action methods.
The action method return partial views, which represent my templates.
Here is an image of my route configuration.
Here is an image of my controller actions.
I am sure I am missing something, but can't seem to figure out what.
This example helps you to understand better about $routeProvider and $locationProvider.
The only issue I see are relative links and templates not being properly loaded because of this.
from the docs regarding HTML5 mode
Be sure to check all relative links, images, scripts etc. You must either specify the url base in the head of your main html file () or you must use absolute urls (starting with /) everywhere because relative urls will be resolved to absolute urls using the initial absolute url of the document, which is often different from the root of the application.
In your case you can add a forward slash / in href attributes ($location.path does this automatically) and also to templateUrl when configuring routes. This avoids routes like example.com/tags/another and makes sure templates load properly.
Here's an example that works:
<div>
Home |
another |
tags/1
</div>
<div ng-view></div>
And
app.config(function($locationProvider, $routeProvider) {
$locationProvider.html5Mode(true);
$routeProvider
.when('/', {
templateUrl: '/partials/template1.html',
controller: 'ctrl1'
})
.when('/tags/:tagId', {
templateUrl: '/partials/template2.html',
controller: 'ctrl2'
})
.when('/another', {
templateUrl: '/partials/template1.html',
controller: 'ctrl1'
})
.otherwise({ redirectTo: '/' });
});
If using Chrome you will need to run this from a server.
Well what worked for me was to remove the setting for the $locationProvider.html5Mode. As someone mentioned in another stack overflow post, here MVC5 and Angular.js routing - URLs not matching using the locationProvider in MVC seems to screw up the routing. I am still to investigate why exactly this happens, as all I thought it did was remove the '#' in the url, but seems like there's more to it

Run module not running

So I have an AngularJS application in which I am attempting to bootstrap only after all of the data for the application is loaded. I need to be able to make the requests in JSONP format so I am attempting to load the $resource module by using a .run statement.
Here's how it looks:
(function(){
// Define our app
app = angular.module("GRT", ["ngResource", "ngRoute"])
.run(function($resource){
console.log($resource);
})
// Configure our route provider and location provider
.config(function($routeProvider, $httpProvider, $locationProvider) {
$routeProvider.
when('/', {
templateUrl: 'views/home.html'
})
.when('/customer-site-registration', {
templateUrl: "views/customer-site-registration.html",
controller: "customerSiteRegistration"
})
.otherwise({
redirectTo: '/'
});
// $locationProvider.html5Mode(true);
});
}())
Basically no matter what I do it wont run that run block. Any ideas?
Run blocks do not run until the Angular application is bootstrapped. I needed this to run before the bootstrapping.
In this setup the ng-app attribute was removed from the enclosing DOM element to prevent auto-bootstrapping and I was doing it manually after running some code.
Since I was only using it to get access to resource, I instead grabbed it manually like this:
var $resource = angular.injector(["ngResource"]).get("$resource");
Hope this helps someone else!

Angular RouteProvider redirect to another html file

I have to build an app for an existing website, but unfortunately the website (outside of my control) detects the user device and redirects to a mobile version.
I am trying to reuse the same js file but different html files.
So I have:
index.html for desktop
mobile.html for mobile
both call init.js where I want to handle my logic, my problem is that for some reason the routing is not working as I expected and I cannot figure out why.
Desktop:
I go to example.com
get redirect to example.com/#/steps/age/0
Refresh the page and it stays in example.com/#/steps/age/0
This works as expected
Mobile:
I go to example.com/mobile.html
get redirect to example.com/mobile.html#/steps/age/0
Refresh the page and instead of staying in the same url, it goes to example.com/steps/age/0#/steps/age/0
This does not work as expected (expected to stay in the same url once refreshing as in the step number 2)
Code below:
angular
.module('profileToolApp', ["ngRoute", "ngResource", "ngSanitize"])
.config(function($routeProvider, $locationProvider){
$routeProvider
.when('/steps/:steps*', {
templateUrl : 'profile-app.html',
controller : 'example'
})
.otherwise({
redirectTo: '/steps/age/0'
});
})
.controller('example', function($scope, $routeParams, $resource, $location){
console.log("example controller");
});
Can anyone please advise?
Thanks.
Angular is examining the entire path to see where it should route to. So when you have example.com/mobile.html#/steps/age/0 There is no matching route, so it substitutes the route for you, in place of mobile.html so you get /steps/age/0#/steps/age/0 from your otherwise. The fundamental problem is that angular has no sense of what mobile.html means, and takes it as a parameter.
One solution is to use routes to separate your pages.
$routeProvider
.when('/', {
templateUrl : 'desktop.html', //was 'index.html pre edit
controller : 'example'
})
.when('/mobile/', {
templateUrl : 'mobile.html',
controller : 'example'
})
.when('/steps/:steps*', {
templateUrl : 'profile-app.html',
controller : 'example'
})
.when('/mobile/steps/:steps*', {
templateUrl : 'mobile-profile-app.html',
controller : 'example'
})
.otherwise({
redirectTo: '/'
});
})
Controllers may vary as needed.
Alternatives to this are to have mobile.html use its own angular App and routing, which may be beneficial since you won't run into desktop directives leaking into mobile. You can inject all of your directives and controllers into it, but still have a nice separation of index and mobile. You can take that a step further and have a redirect to m.example.com, but that's a different topic.
EDIT I made a mistake. Having templateUrl be index.html is a bit wrong. index.html should contain your ng-app and your ng-view directives, possibly a controller. Any common html should reside there. desktop.html and mobile.html should contain the specific HTML for those platforms.
As an afterthought, Within those you could have a directive called profile that does all of your profile work, and with a bit of ng-switch you can have that appear if steps is defined in the scope, and use:
$routeProvider
.when('/steps?/:steps?', {
templateUrl : 'desktop.html', //was 'index.html pre edit
controller : 'example'
})
.when('/mobile/steps?/:steps?', {
templateUrl : 'mobile.html',
controller : 'example'
})
But now I'm rambling, I'm not 100% sure that will work tbh. END EDIT

$routeProvider not firing Controller

I have a situation where the Angular $routeProvider appears to not fire controller actions on route changes.
The routes are super simple urls:
window.app = angular.module('app', ['ngRoute', 'app.filters', 'app.services', 'app.directives', 'app.controllers'])
.config([
'$routeProvider', function($routeProvider) {
console.log("app.js config launched");
$routeProvider
.when('/nav', {
templateUrl: 'temp/test.html',
controller: 'navController'
// controller: function($scope) { alert('scope called.') }
})
.when('/home', {
controller: 'homeController',
template: ' '
});
$routeProvider.otherwise({ redirectTo: '/home' });
}
]);
The controller is just an log out to verify access:
app.controller('navController', [
"$scope", "cellService",
function ($scope, cellService) {
console.log("**** navController fired");
}
]);
The initialization code fires so the routing is initialized. When I hit:
http://localhost:4333/app/#/nav
and the url changes I can see that the test.html template is accessed by the browser, but the controller never fires.
This seems to indicate the route is getting activated by the URL change, but for some reason the controller is not firing. I've tried using a function instead of a controller name, but that too never gets fired. I've also verified that the controller is valid by attaching ng-controller="navController" to an element and that fires the controller just fine.
This is a page that originally didn't have routing associated as it was basically single self-contained page that didn't need navigation. I added the route code after the fact. I added an ng-view (there wasn't one before) after which at least the template started loading - without ng-view nothing happens.
Stumped and not sure what else to look at. Help.
It turns out the problem really was operator error on my part, but I think it's a scenario that can cause issues so I'll use this as the answer.
The issue that caused this problem were two-fold:
The HTML template HTML page (via templateUrl) had an invalid URL so the page never loaded
and the controller wasn't fired because of that.
When switching to a template I used an empty template (" ") but had also
removed the ng-View directive. The ng-View directive MUST BE present
even when using an empty template. Without it the controller doesn't fire.
In both cases it didn't work and I mistakenly assumed that the controller was not getting fired which was confusing because it did fire if I explicitly hooked it up with ng-controller.
Yup plain operator error, but the latter is vitally important - without ng-View the controller doesn't fire.
What happens if you define the function externally and reference that? So instead of
.when('/nav', {
templateUrl: 'temp/test.html',
controller: 'navController'
})
It would be
.when('/nav', {
templateUrl: 'temp/test.html',
controller: navController
})
and elsewhere
function navController($scope, cellService){
console.log("**** navController fired");
}
navController.$inject = ['$scope', 'cellService'];

Angularjs - how to clear $routeProvider's caches of templateUrl

I have really basic use case in my app where I use AngularJS (1.0.8) for front end and Grails for back end. In the app layout I have a language switcher which allows the user to change the language. Switching the language, it does new http request to retrieve the page. Grails renders all language related stuff (i.e. labels) properly translated. This only works for Chrome, FF, and so but not for IE. IE renders proper language just for layout which is rendered by the main request.
I located the problem. I have defined $routeProvider where I load major of the app content. It is cached by default, therefore IE doesn't load templateUrl of $routeProvider because it loads them from cache:
myApp.config(function ($routeProvider) {
$routeProvider.
when('/', {controller: 'MyCtrl', templateUrl: '/eshop/myConfig'})
});
What I don't get is why it works in all other browsers.
I found some post how to clear cache but they doesn't work for me. Is there any solution for me? If not, I find $routeProvider completely useless for my use case.
Post I found:
angularjs clear history when view loaded
AngularJS disable partial caching on dev machine
Below should do it. You can manipulate angularjs's template caches by using $templateCache, so $routeProvider will load the template as new every time you access the controller.
myApp.config(function ($routeProvider) {
$routeProvider.
when('/', {controller: 'MyCtrl', templateUrl: '/eshop/myConfig'})
})
.controller('MyCtrl', function ($scope, $templateCache) {
$templateCache.remove('/eshop/myConfig');
// or
$templateCache.removeAll();
});
I was having the same issue with $routeProvider. And yes, the $templateCache does not help in this situation. Instead of keeping finding the real 'cache' source, I added the stamp parameter after the templateUrl.
In my code:
$routeProvider.
when('/', {templateUrl: '../views/home.html?v='+window.buildNumber, controller: 'HomeCtrll'}).
when('/report', {templateUrl: '../views/form.html?v='+window.buildNumber, controller: 'FormCtrll'}).
otherwise({redirectTo: '/'});
Sadly, I used a global variable "buildNumber" to save my life. Because I also use RequireJS for my AngularJS project, so this "buildNumber" will also be added to every dependency JS file by using the code:
require.config({
urlArgs: "v=" + window.buildNumber,
paths: {....}
});
Then every time the JS source or template html has been changed, I will only need to update that "buildNumber" in global scope. This is just a thought for the future updates in production environment. Hope this helps.
So the only solution I found was to completely disable cache for ajax queries. I found the solution here: https://stackoverflow.com/a/19771501/607038
myModule.config(['$httpProvider', function($httpProvider) {
//initialize get if not there
if (!$httpProvider.defaults.headers.get) {
$httpProvider.defaults.headers.get = {};
}
//disable IE ajax request caching
$httpProvider.defaults.headers.get['If-Modified-Since'] = '0';
}]);
I don't like this solution because it disable cashing for the content which is really static. So if you have better solution than share it.

Resources