How to update a route from inside an AngularJS controller? - angularjs

I have an AngularJS application into which I want to load some plugins that are discovered by a controller when it starts up. In order for the plug in to work I have add some routes to the $routeProvider from the controller, but there seems to be no way to do this.
Right now I'm using a pretty ugly hack as below:
var routeProvider;
angular.module('example', [], function($routeProvider) {
routeProvider = $routeProvider;
// Set up other routes
}
function Controller($http, $location, $timeout) {
// Use $http to find some plugin
routeProvider.when(plugin.url, plugin.options);
// Ugly hack so that the plugin appears if $location.path() already points to it
var path = $location.path();
$location.path("/");
$timeout(function() { $location.path(path); }, 10);
}
If I don't do the nonsense with $timeout then if I start (load the page) at the route for the plugin it won't load ($route.current remains blank). With the jump between paths the route gets resolved properly and the plugin view loads as it should.
Is there a better way of doing this?

You could tear $routeProvider from the source and make your own version? :-)
$routeProvider is just a provider made for you. https://github.com/angular/angular.js/blob/master/src/ng/route.js

The way that we did this in the end was to have all of the routes downloaded from services before we start up AngularJS and then use that data structure to set up the routes, then bootstrap Angular once we had the information and everything was set up properly.
The only downside of this is the delay in start up, especially if there are multiple service plug ins that you need to handle.

Related

Angular js use $windows from directive code

OK, just when I think I understand AngularJS I get zapped.
I have an application that uses a number of different google maps. I want the user to click on a marker and then have the system so to a new screen with information relating to what was clicked.
Everything is working well up to a point. I get the click event and then get ready to go to the appropriate screen. My code at that point looks like:
$window.location.href = "#/" + ScreenName + "/" + Parameter ;
At this point I get the error:
ReferenceError: Can't find variable: $window
Which searching tells me I need to inject $window
I have tried a bunch of different ways to do this injection, but this is also where my personnal knowledge base fails me.
I think I need to have my app.js file look like this:
.config([
'$routeProvider',
function($routeProvider)
{
$routeProvider.
when("/customer/:cust_gid", {templateUrl: "views/div_Cust.html", controller: "customerController"}).
when("/location/:locn_gid", {templateUrl: "views/div_Locn.html",controller: "locationController"}).
otherwise({redirectTo: '/utilities'});
}])
.config(function ($windowProvider) {
var $window = $windowProvider.$get();
console.log($window);
});
This does nothing for me. I need to know If I am close and just don't have the syntax right or is there something else missing.
Do I need an include file in my index.html file to load $windows?
Can someone give me a kindergarten level answer to this question.
Appreciate
Stan
$windows is a globally service which is included in angularjs.
You don't need an extra library reference for it.
To use it, just inject is as any other service in the controller which should use it
app.controller('locationController', function($scope, $window)..

Angular Template Cache with Service Worker Issue

I have a gulp setup that puts all my html in template cache for faster access in angular. I'm trying to add a service worker to my project, using sw-precache, so that it can be used offline. If I'm connected to the network, everything works fine. When I go offline, the requests for html resources (that are in the template cache) fail because it seems it is requesting the path from the network.
Is there something I need to add to my sw-precache config in order to have it defer to angular to handle retrieval of html files?
ok, so this is how I solved this. I am using Angular JS 1.6.4 with sw-precache.
I have CacheStorage via service workers, so using service workers I know I am expecting devices to support certain functionality, in my case we know our users will have Android Tablets with Chrome and support is valid.
I am developing a progressive web app with offline functionality.
So, the theory...I have directives which have templateUrls.
Using this post: https://thinkster.io/templatecache-tutorial
I basically have my directive code:
angular.module('app').directive('location',
['$timeout', 'notify.service', 'user.settings.service', 'log.service',
function ($timeout, notify, userSettings, log) {
return {
restrict: 'EA',
... controller etc..,
templateUrl: '/App/core/directives/location.html'
}
}
]);
Now, when this app goes offline, the cached instances of the content was not kicking it - annoying.
So, after much procrastinating I got down and dirty.
My solutio is, keep the templateUrl as it is, but overwrite the content via the $templateCache service.
To do this, you append a RUN function with your directive (for clarity). We know the service worker cache representation of our Url files contains the common path, in my case: '/App/core/directives/location.html'.
So, using new technology in the browser, window.caches gives me access to the CacheStorage that the service workers uses, I can then use the API available: https://developer.mozilla.org/en-US/docs/Web/API/Cache
I can then use the match method to find the matching service worker cache content, read that stream of binary and convert to HTML and then tell $templateCache to replace it with the service worker cached value.
So, for completeness (and you can create a common service which replaces the cached values based on templateUrl - which I will be doing for each directive)
(function () {
'use strict';
var templateUrl = '/App/core/directives/location.html';
// <location on-location='someFunc'></location>
// provides a form/control to find a location either by GEO location or manual city search
angular.module('app')
.run(['$templateCache', function ($templateCache) {
var cache = window.caches;
cache.match(templateUrl, { ignoreSearch: true }).then(function (response) {
if (response) {
response.body.getReader().read().then(function (cachedResponse) {
// convert the Uint8Array value to HTML string
var content = new TextDecoder('utf-8').decode(cachedResponse.value);
$templateCache.put(templateUrl, content);
//console.log($templateCache.get(templateUrl)); // debug
});
}
});
}])
.directive('location',
['$timeout', 'notify.service', 'user.settings.service', 'log.service',
function ($timeout, notify, userSettings, log) {
return {
restrict: 'EA',
... controller, scope etc...
templateUrl: templateUrl
}
}
]);
})();
Draw backs...the RUN process is synchronous, so initially they have to hit the site online first...but thats how service worker needs to work anyway, so thats handled in training :)
I expect there to be a better option, but for the time being thats the solution I have, I will be creating a template service for replacing $templateCache values based on the var templateUrl each directive will have, so the code becomes cleaner in each directtive....i considered having a global arr of templates and files, but, just a bit obscure, think its cleaner for each directive
my original solution was not 100%
To solve this, use sw-precache and sw-toolbox
Using a gulp configuration you can setup sw-precache to cache you content and extend this with sw-toolbox to use cached responses based upon routing configuration
see: https://developers.google.com/web/ilt/pwa/using-sw-precache-and-sw-toolbox

Dynamically loading controller with angularjs and requirejs

I am trying to integrate angularjs app with requirejs. I want to preload templates and controllers on-demand. In my example:
http://plnkr.co/edit/vIps7t92OFzA5RXoTjvI?p=preview
I init controller SocialController inside the app.js and I need to load dynamically StreamController inside the SocialController. Unfortunately I am getting an exception, see browser console.
Argument 'StreamController' is not a function, got undefined
If I remove from SocialController
angular.module('sampleTest').controller('StreamController', StreamController);
and add it to app.js, it works but in this case requirejs will preload it right at the beginning and not when I need, inside the SocialController on demand.
Here is an answer. Looks like angular does not allow instantiating any new services or controllers later on, after the app startup. So the code below I had to add to app.js files at the bottom, after all the controller and services are init. This is a hack but fixes the problem for lazy loading with requirejs.
See my link to plunkr with the fix. Now there are no exception and StreamController is lazy loaded and init after the startup.
sampleApp.config(
function (
$controllerProvider,
$compileProvider,
$filterProvider,
$provide
) {
sampleApp.controller = $controllerProvider.register;
sampleApp.directive = $compileProvider.directive;
sampleApp.filter = $filterProvider.register;
sampleApp.factory = $provide.factory;
sampleApp.service = $provide.service;
}
);

Defining routes outside app.js

I have been told that in AngularJS, you are supposed to do all your routing in one file. Now, I feel like there's a fundamental problem with that. If you have a large web application, it doesn't seem to make sense to send every single route possible to the user. This would mean, that your main JavaScript file could end up with routes that look like path/:to/a/part/:of/my/application.
I'm used to a different way of routing, the main router has certain routes (e.g. /admin, /blog, /about). All the logic, such as further routing, for the blog is handled in (e.g.) blog.js. Within blog.js, there are routes such as /latest, ending up as the final route /blog/latest.
How would one do this properly within Angular? Or am I completely missing the point with Angular?
You don't have to do all your routing in one file, or in app.js for that matter. It just has to be done in a config block. Since your Angular app should be defined in the global namespace, you could have a separate file that starts like this:
yourAngularApp.config([
'$routeProvider',
'$locationProvider',
function ($routeProvider, $locationProvider) {
// Your routing code
]);
Thus you could separate your routing code however you like.
See more anout config blocks here under the headings "Module Loading & Dependencies" and "Configuration Blocks:" https://docs.angularjs.org/guide/module
The thing I am trying to achieve is only exposing the /blog/latest
route once you request the page /blog, it's not relevant to know
/blog/latest exists if you're not interested in /blog either.
If navigating to /blog caused the page to refresh, this goal would not be difficult to accomplish. You could structure your app in such a way that
your main routes are defined in a single file, and then
your further, /blog-related routes are in a separate file that only gets downloaded when you're in that area of that application.
However, if your application is a full-on SPA, and Angular does not get bootstrapped again when the /blog page is requested, this approach would not work. There is a more-complex approach I've read about that involves saving a reference to the routeProvider for later use:
yourAngularApp.config([
'$routeProvider',
'$locationProvider',
function ($routeProvider, $locationProvider) {
// Your routing code
yourAngularApp.routeProvider = $routeProvider;
]);
Then, you can utilize yourAngularApp.routeProvider later on (in a controller, for instance) to further configure your routing.
I found an even cleaner solution solution than that here:
Deferring Route Configuration Using Decorators And Route Resolution In AngularJS
The gist of that approach is that you can create a $route decorator during the configuration phase, and use that to expose the $routeProvider functionality.
For completeness, here are the main bits of that code. First you define the decorator to modify $route during the configuration phase, exposing whatever functionality from $routeProvider you like (my example just shows .when()):
yourAngularApp.config([
'$routeProvider',
'$locationProvider',
function ($routeProvider, $locationProvider) {
$provide.decorator( "$route", routeDecorator );
function routeDecorator( $delegate ) {
var $route = $delegate;
$route.when = function( path, route ) {
$routeProvider.when( path, route );
return( this );
};
return( $route );
}
]);
Then you can inject $route into other areas, like controllers and services:
yourAngularApp.controller(
"YourController",
function($scope, $route) {
// code that modifies routing here
// ...
$route.reload();
}
);

How to set in angularjs templateUrl to location.pathname in otherwise clause

I'm new to angularjs and want to integrate it in a cakephp app. For some pages I don't have a controller since no javascript is exectuted there or because I still have to create them. I however don't want to list them all in the routes. For this reason i set it like the following:
angular.module('desktop', []).
config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$routeProvider
.when('/', {templateUrl: 'pages/index', controller: IndexController})
.when('/clubs', {templateUrl: 'partials/clubs.html',controller: ClubListController})
.otherwise({templateUrl: location.pathname});
}]);
This is however not working. When I go to /help, nothing happens. What am i doing wrong?
From my comments:
As far as I know it there is currently no way to do this with just routes. Routes are made to be static, they are defined as the app loads and do not update dynamically as time goes by. So using location.pathname() (or directly checking window.location) won't work since the route be set to whatever the value is when the app starts, and then never change again. It won't update when you load a new path unless you do a full browser reload (this is btw possible, but a hacky sollution).
But people have been working around it using includes, which might work for you depending on what you are after. See this question and the accepted answer for an example of how this works.
AngularJS - How to use $routeParams in generating the templateUrl?

Resources