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
Related
I have an angular SPA with several modules, services, factories and controllers. I have written a helper service and put it in a common folder minifed js file that all my html pages reference. They contain common bits of data obtained by an AJAX call to a database. It has to run first because the rest of the app depends on values from this helper service. The issue is that the service returns out before the promise has been returned successful, so the helper service is always empty. I do not know what the best approach is to write a helper ajax call and have all files within an angular app reference values returned by it (so everything has to wait before the promise comes back and populates the helper service). I cannot put it in the scope at the top of the main controller, because factories and services cannot reference scope variables is that right? At least I dont understand how if so.
Have searched around and found lots of ways to reference a common service from multiple controllers, but little assistance on how to access that information if the data is a result of an ajax call.
Thanks in advance.
The issue is that the service returns out before the promise has been returned successful, so the helper service is always empty.
Try using resolve:
https://github.com/angular-ui/ui-router/wiki#resolve
https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
Example syntax:
let routerConfig = ($stateProvider, $urlRouterProvider) => {
$urlRouterProvider.otherwise("/login");
$stateProvider
.state('login', {
url: "/login",
templateUrl: "templates/login.html",
controller: "LoginCtrl"
})
.state('home', {
url: "/home",
templateUrl: "templates/home.html",
controller: "HomeCtrl",
controllerAs: "home",
resolve: {
entries: function($rootScope, database) {
return database.getEntries($rootScope.user.uid);
}
}
})
}
There was no point displaying HomeCtrl so I ensured entries from database get resolved before loading... I think you can use similar technique in your code.
If you're using a central service that other pieces of the application depend on, you can use two approaches.
Approach one would be to give the top level controller access to the data and then pass it down to all other components.
The other approach would be giving each individual component/controller direct access to the data.
Either way the code would be kind of similar. There's plenty of ways to do it but the simplest solution I use is just taking advantage of object reference:
Factory:
//you can use factory or service doesn't matter.
.factory('dataFactory', function(){
var store = {
data: null
};
//Return the entire store object
function subscribe() {
return store;
};
function fetchData() {
$http.get('someUrl.com')
.then(function(res) {
//Set data to a property on the store.
store.data = res.data;
});
}
return {
subscribe: subscribe,
fetchData: fetchData
};
}
Controller:
.controller('someController', function(dataFactory) {
//Essentially copying over the store object in the factory by reference
this.store = dataFactory.subscribe();
//call the ajax
dataFactory.fetchData();
}
Template:
<div ng-controller="someController as ctrl">
{{ctrl.store.data}}
</div>
Pros
Any controller in the application can get access to the store in the factory
Since they are referencing the same object, you don't need to do any crazy event emitters or anything to notify all the controllers if the data gets updated in the factory.
You are also caching the data in the factory. If any controllers get destroyed and re-created they can just subscribe again. The data doesn't disappear.
Cons
It's bound by object reference, so any changes made to the store object anywhere in the application will change the object everywhere. This can be good or bad depending on what you want to happen in your application.
If you are working on a large application this can get messy, especially if you're not the only one working on it. However if this is what your application requires and you need a more robust option, i suggest checking out state management tools such as redux.
Checkout this codepen I made which implements a very simple version of redux.
I was wondering what the best moment is while initializing the app to retrieve data from:
1 - a REST service
2 - $routeParams
to define application wide constant.
config phase only accepts providers and during the config / run phase $routeParams properties are undefined.
This seems like somewhat in the right direction:
Can you pass parameters to an AngularJS controller on creation?
Also: how to define a constant within a controller, is that possible?
app.controller('MainCtrl', function($scope) {
//define app.constant here
}
--edit: typo
During run phase all the providers should be initialized and working correctly, you can use the $http service to retrieve what ever parameters are needed for you.
I am pretty sure the $routeParams are initialized at run phase as well.
Defining constants in a controller isn't a good practice (in my opinion), if they are unique to that controller then they are just variables, and if you want real application wide constants, use a service, that's what they are for :)
I know of one easy way to pass parameters to controllers on creation, which is using the angular ui router project: https://github.com/angular-ui/ui-router
In the resolve function you can do http calls if necessary, inject constants etc, it's very handy and personally I never build an angular project without it.
I am pretty sure there are more ways, but usually the best practice to pass data between controllers is using a service.
On a side note, if you have a piece of data that is common to more than 1 controller, the easiest way is to put that data on a service and do a watch on that service return value, for example, say I have isLoggedIn, which can change at any moment and a lot of controllers will want to be notified about it, use a UserService and watch for it's value:
UserService.isLoggedIn = function() {
return _isLoggedIn;
}
And in your Controller:
$scope.$watch(function() {
return UserService.isLoggedIn();
}, doSomeAction);
I hope this helps!
This http://www.jvandemo.com/how-to-resolve-angularjs-resources-with-ui-router/ seems like a nice guide, basically, you add the resolve to the state:
.state("customers", {
url : "/customers",
templateUrl: 'customers.html',
resolve: {
//any value you want, this function should return a promise,
//only when that promise is resolved, it will instantiate the controller
//Make sure however you add some signal that something is happening because
//while fetching it can seem like the page is not responding
customers: ['$http', 'anyOtherServiceYouMightNeed', function($http, otherService){
//return a promise
return $http.get('api/customers');
}],
//and for constant
constants: ['ConfigService', function(config) {
return config.appConstants;
}]
},
//customersCtrl will have customers resolved already
controller : 'customersCtrl'
});
app.controller('customersCtrl', ['$scope', 'customers', 'constants',
function($scope, customers, consts) {
//customers will be ready and resolved when the controller is instantiated
//you can do this with anything you might need inside a controller
}
I've got the guts of a routing architecture in Angular that will dynamically download and inject Angular Controllers and Services ... the Controller part works fine, and I'm trying to download dependant services via $route's .resolve property.
Now, say if I have a factory declared in scope while the page starts up, it registers fine and Controllers that use it resolve fine, e.g:
myModule.factory('MyInjectedDep', function() {
return {};
});
....
MyController = function(MyInjectedDep)
But if I try and register that dependency at "run time" (for want of a better phrase), I get a Circular Dependency error. e.g:
$route.routes[routeItem.route] = {
resolve: {
MyInjectedDep: ['$injector', function($injector) {
// In real code I download/eval this via $http but same behavior occurs
myModule.factory('MyInjectedDep', function() {
return {};
});
}]
}
}
So when my Controller is then initiated:
MyController = function(MyInjectedDep)
I get a circular dependency error, but no dependency trace in the error message?
Error: Circular dependency:
Any ideas appreciated
The key is latching onto $provide at configuration time. If you grab $provide at configuration time and maintain a reference to it, you can use it to register your factory like:
$provide.factory.apply(null, ['MyInjectedDep', [function() {
return {};
]}]);
I have a provider/service designed to do this, adapted from some other samples on github: https://github.com/afterglowtech/angular-couchPotato .
It's primarily designed to load from AMD, but you could probably use it's registerXXX functions with $http, or at least copy the relevant portions of its code. Don't let the size of the repository fool you -- the actual provider/service is about one page of code https://github.com/afterglowtech/angular-couchPotato/blob/master/src/couchPotato.js .
OK, here's the deal. I've got a service that deals with both $resource and $scope, and I'm not familiar enough with Angular.JS to trust myself to organize it properly. The service can associate resources with WebSockets that maintain active connections to the backend. Whenever the backend notifies the service of a change to a given resource, the resource's attributes are changed to match, and thus the web page is updated automatically in real time with new values.
This Websockety goodness is called "Frisch", and the way I have it currently organized is thusly:
The Frisch class is traditional and completely independent of the Angular.JS module system. It has an angularize method that sets up Angular.JS resource bindings:
var Frisch = function(url) {
... create a websocket ...
this.angularize = function(scope, record) {
this.websocketCallback = function(attributes) {
... update `record` with the new values ...
scope.$apply(); scope.$digest();
}
};
};
Meanwhile, my controller looks like this:
var MyController = function(MyModel, $scope) {
$scope.myModel = MyModel.get(... stuff ...);
new Frisch('/some/websocket/url').angularize($scope, $scope.myModel);
};
MyController.$inject = ['MyModel', '$scope'];
So this is my first Angular.JS project, and I feel like things could definitely be organized better. Specifically, it feels weird that both $scope and the resource must be passed to angularize.
I'm guessing there's a much more Angular-y way of doing this, like with services or providers or something. Maybe some way of "mixing in" Frisch-ness into a model factory (in this case, MyModel). But I can't quite wrap my head around how to do it...
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.