Adding an AngularJS resource that may or may not exists - javascript

Using AngularJS 1.0.4
One of our Angular apps is dependent on a resource being loaded before anything else can be loaded. We do this from a service that gets initialized in app.run() and then broadcast an event that everything else listens for to start loading.
In the controllers we also need to have access to the resulting resource. So I then have the following in each one:
$scope.parent = null;
if(!svc.parent) {
$scope.on('parentLoaded', function() {
$scope.parent = svc.parent;
});
} else {
$scope.parent = svc.parent;
}
Each of the controllers is tied to a view and can be called in any order. So it's not guaranteed that the resource is loaded when the controller gets called, although it can be if another controller was called before hand. The load event only gets trigger the first time the service is initialized when the app first loads.
Is there a better way to this?
It seems kind of redundant & not clean.

I would just use a promise. You would have something like:
var deferred = $q.defer();
$http.get('/application').then(function(res) {
deferred.resolve(res);
});
function fetch() {
return deffered.promise;
}
To load your initial resource, we'll call the resource "application" for example. Then, to load your next portion, you can do:
application.fetch().then(function(svc) {
//res is whatever is returned from our $http.get, earlier
$scope.parent = svc.parent
//do whatever required your resource here
});

Instead of doing this:
$scope.parent = null;
Create an empty array instead (or object depending on what resource is).
$scope.parent = [];
This way angular object watchers will react to changes of the array. For more advanced issues can use promises when loading data

Related

ko.pureComputed - clear cached value on sleep

I have some ko.pureComputed properties that usually hold a big amount of data inside themselves.
When those ko.pureComputed properties go to sleeping state (noone is subscribe to them) I don't need that data anymore until they go back to listening state (someone is subscribe to them).
During that time while they are in the sleeping state I'd like the ko.pureComputed properties to clear their values so that the garbage collector can remove that computed data from memory, then when I need the computed data again, that is, when the ko.pureComputed go back into listening state, I'd like to reevalute the computed data.
Is that possible?
Further details about my use-case scenario:
My site is a Single Page Application, meaning a Javascript framework (Durandal) switches pages (HTML and JS) in display for the user.
Some pages have a need for computed properties which would store large amount of data. I'd like to use ko.pureComputed for that purpose, because it will stop updating itself once the user goes off its page, i.e. once the ko.pureComputed goes into sleep state because it has no more listeners.
(Durandal deattaches and reattaches the page's JS viewmodel from and into the HTML view when the user goes away or visits the page)
The problem is that the ko.pureComputed keeps its latest value cached.
In my case those values are large arrays of large objects, which take up a noticeable amount of memory. I'd like to dispose of that data once it's not needed anymore.
Is there a way to clear the cached value from the ko.pureComputed once it goes into the sleeping state (when the user leaves the page), and then later reinitialize it when the ko.pureComputed goes back to listening state (when the user revisits the page)?
Using a pure computed's state change events, we can tell the computed to clear its value while it's sleeping. Here's a wrapper function that sets it all up:
function computedValueOnlyWhenActive(readFunction) {
var isAwake = ko.observable(false),
theComputed = ko.pureComputed(function () {
if (isAwake()) {
return readFunction();
}
});
theComputed.subscribe(function() {
isAwake(true);
}, undefined, "awake");
theComputed.subscribe(function() {
isAwake(false);
theComputed.peek(); // force reevaluation
}, undefined, "asleep");
return theComputed;
}
Demo: https://jsfiddle.net/mbest/gttosLzc/
This isn't an answer to the specific question you asked, but it might be a more helpful answer depending on your situation.
In Durandal the router plugin navigates by asynchronously loading the specified module with a requireJS call. Once it retrieves the module it checks if the result is either an object or a function, and if it's a function it will instantiate a new object from the function. If it is an object it just uses the object.
RequireJS automatically caches the modules it retrieves in that it doesn't bother re-fetching a module from the server if it's already downloaded it. So if your module definition is a plain object then that same object will get displayed each time.
This module definition will save its state between navigations:
define(['durandal/app'], function (app) {
var title = 'myView';
var vm = {
title: title;
};
return vm;
});
This module definition will create a new object and will re-bind all knockout bindings resulting in a freshly loaded screen on each navigation.
define(['durandal/app'], function (app) {
var title = 'myView';
var vm = function(){
this.title = title;
};
return vm;
});
EDIT:
For a more granular durandal solution that also works with older versions of knockout (i.e. before pureComputed) you can combine the concept in michael best's answer of using an isAwake observable with durandal's view activation and deactivation lifecycle hooks.
function viewModel(){
var self = this;
this.isAwake = ko.observable(true);
this.theComputed = ko.computed(function () {
if (isAwake()) {
return myValue();
}
return "";
});
this.activate = function(){
self.isAwake(true);
}
this.deactivate = function(){
self.isAwake(false);
}
}
var vm = new viewModel();
return vm; //return the instance not the function
http://durandaljs.com/documentation/Hooking-Lifecycle-Callbacks.html

Reflux avoid hitting server every time, when data cached locally

I curious if there is any agreed upon pattern to check if data has been already loaded before hitting the server.
Say I have my action that looks like this:
Actions.loadRequest.preEmit = function () {
$.get('/store/', function (data) {
Actions.loadSuccess(data);
}.bind(this));
}
This is called from a component that is simply saying give me this data:
But I don't want to hit the server if that data is already in the store.
Should I store the logic of checking the store in the component:
render: function () {
var data = this.state.store.data;
if (!data) {
Actions.loadRequest();
}
Is there a better way to go about this?
In my project I use shouldEmit for this (see https://github.com/reflux/refluxjs#action-hooks). An example from my code:
var streamStore = Reflux.createStore({
[...]
});
actions.loadStream.shouldEmit = function(streamId) {
if(streamId in streamStore.data)
return false;
return true;
};
This lives in the same file as the store definition. I think this is conceptually the right approach because the store saves the data, so the store should be responsible for intercepting the request to load more data and saying not to, just as it's responsible for listening to the action saying more data is available and updating itself.
Unfortunately this won't work with your example because you bound the AJAX call to preEmit, which gets called before shouldEmit. I would suggest refactoring to make the API call in a normal listen call, like this:
Actions.loadRequest.listen(function () {
$.get('/store/', function (data) {
Actions.loadSuccess(data);
}.bind(this));
});
This saves preEmit for the rare case of needing to rewrite an action's arguments before emitting it. I do use this pattern in my code, for example when loading a second page of results, which relies on a next token that came with the first page and is thus in the store. But in the general simple case of "action triggered, so make a request", using listen makes more sense because then you can add preEmit and shouldEmit for more advanced behavior, like the caching you want.
Reflux also has a helper function, listenAndPromise, which further simplifies the common use case of "action fired, make AJAX call, then fire another action when it's done". Your example could become:
Actions.loadRequest.listenAndPromise(function () {
return $.get('/store/');
});
See this section of the docs for more info on how to set that up: https://github.com/reflux/refluxjs#asynchronous-actions

AngularJS : Filtered data not updating when underlying data changes

I am pretty new to AngularJS and have been working a lot with KnockoutJS bear with me a little as I still haven't quite got my head around when Angular can and cannot track changes.
I am building an app that will have an array of underlying data which initially I will poll and update, but later will improve to push from the server. All data in the app will then just be transforms or filters based on this data. So I have a service to fetch the data and to also fetch the commonly filtered versions of the data like so:
.factory('Scores', function ($resource, Utils, $q, $interval, $filter) {
var scoresResource = $resource('http://localhost:8000/scores'),
scoresData = [];
$interval(function () {
scoresResource.query(function (newScores) {
scoresData.length = 0;
angular.forEach(newScores, function (dataEntry) {
scoresData.push(dataEntry);
});
})
}, Utils.TIMES.SHORT);
return {
getAll: function () {
var deferred = $q.defer();
if (scoresData.length > 0) {
deferred.resolve(scoresData);
} else {
scoresResource.query(function (allScores) {
scoresData.length = 0;
angular.forEach(allScores, function (dataEntry) {
scoresData.push(dataEntry);
});
deferred.resolve(scoresData);
});
}
return deferred.promise;
},
getByLeagueName: function(leagueName) {
return this.getAll().then(function (allScores) {
return $filter('filter')(allScores, function (score) {
return score.League === leagueName;
})
})
}
}
});
And my controller simply fetches the filtered data and adds it to the scope.
.controller('LivescoresCtrl', function ($scope, $stateParams, Leagues, Scores, $interval, Utils) {
Scores.getByLeagueName($stateParams.leagueName).then(function (scores) {
$scope.scores = scores;
});
})
But it seems that the filtered data is not automatically updating when the underlying data updates. I would like to avoid using filters in the view as at times I need to combine data together in ways that I cannot easily achieve.
So I guess my question is why does this not update when the main data updates, and is this a valid approach in an angular world. I could hit the backend for all variations of the data, but as this is a mobile app and all data is needed in the app at all times I don't really want to make extra requests just to combine or filter the data.
Thanks,
You are only setting the data on the controller once, using the promise returned by "getByLeagueName". A promise only ever resolves once!
I'm not sure what ko.computed really does. But you create a new array inside of getByLeagueName that is in no way linked to the original array, by using the filter inside of the service. The new array doesn't "know where it came from"!
It seems you're trying to implement a polling update, which you should do inside the controller, not the service. Think of services as the containers for backend logic, without access to the scope - that's what controllers are for. And as long as you're not working with the scope directly, Angular won't ever update the visible data, because that is the only place that an Angular app gets the displayed data from.
The typical Angular way to use filtered data would be: Make the original data, after fetching it, available on the controller. Then use a filter inside of your view (HTML), i.e.: <div ng-repeat="entry in data | filter: someFilter">. See ng-filter. This way Angular knows when the original data changes, runs the filter on it again, and the UI will update effortlessly.
If you really need to use that filtered data in some other places than the view - and make sure you do - then there's some approaches to that. One is to use the service to notify the controller of data changes: Listen to an event inside the controller via $rootScope.$on, and emit that event in the service via $rootScope.$broadcast.
You can also have a look at this repository, which takes a promise-based approach to polling data, maybe it works well for your task.

Handling Page Refresh with AngularJS Service and Views

I have a simple SPA with two views: a list view and a detail view. I use a service called StateService to pass data between the two controllers.
I am trying to handle the case where the user refreshes the browser page--when this happens, the StateService gets reinitialized and the detail view can no longer work. I want to detect when this happens and return the user to the list view.
Here is a simplified version of my State Service. The idea is that I would set isInitialized to true when I switch to the detail view so that I can detect when the service has not been properly initialized.
var StateService = function () {
var isInitialized = false;
};
This is what I have tried in the first few lines of my controller. The StateService is being successfully injected into the controller.
//always returns [Object], on refresh or navigating from list page
alert(StateService);
// this next line always returns undefined. Should be false since I am initializing
// the value to false?
alert(StateService.isInitialized);
//One of the many combinations I have tried . . .
if (!StateService.isInitialized | StateService.isInitialized == false) {
$location.path('/');
}
I don't know if this is a gap in my understanding of javascript or angular, but any thoughts on how I can get the above code to work, or better ideas on what to do when a user refreshes the page?
Edit
Using console.log as recommended by nycynik I see the following:
c {} [StateService]
undefined [StateService.isInitialized]
So it seems that StateService itself is just an empty object when this code gets hit. I get the same results from my other controller (the one that handles the list view).
As noted in the comments, the service seems to otherwise work as expected.
I think you have a problem with scoping. variables in javascript have function scope.
isInitialized is scoped only to your StateService Function, so you can't get at it outside of your StateService Function.
not sure exactly how you're getting this thing into your controller, but maybe these help:
if you're using an angular's module.service() to use StateService as a constructor to inject a (new StateService) into your controller then you need to set isInitialized on the instance
var StateService = function () {
this.isInitialized = false;
};
This way (new StateService).isInitialized === false
If you are just using module.factory() or something else that doesn't use new, then you need to put your isInitialized value somewhere else you can actually get at it.
var StateService = function () {
};
StateService.isInitialized = false
Hope that helps.

How do I let my controller know of the status of loading data?

I'm working on a large AngularJS app in which I am trying to encapsulate all my Ajax code into various services which the controllers get data from. The problem revolves around needing to know the status of any ajax calls and displaying the correct information to the user. There could be no data found, data currently loading, or an error that has occurred preventing data from being loaded. The user needs to be shown a loading message, a "no data found" message, or an error message.
Let's say I have a ProjectService. Ideally if there was a method called getAllProjects it would return an array of projects. But that way I have no idea what is happening with the server communication.
So how to I let the controller know if data is loaded, loading, or an error has occurred? The best way I can come up with is using callbacks like in the pseudo code below. Is there any better way to accomplish such a thing or anything I may be overlooking?
Thanks.
app.controller( "ProjectController", function( $scope, ProjectService ){
// Set the initial / default status
$scope.loadStatus = "loading";
// Return an empty array initially that will be filled with
// any data that is returned from the server
// The callback function will be executed when the ajax call is finished
$scope.projects = ProjectService.getProjects(function( status ){
// Alert the controller of a status change
setStatus( status );
});
function setStatus( ){
$scope.loadStatus = status;
// ... update the view or whatever is needed when the status changes....
}
});
app.service( "ProjectService", function( $resource ){
return {
getAllProjects: function(){
// ... load and return the data from the server ...
}
};
});
In our codebase we've just been doing
$scope.flags.loading = true;
$http(...).success(function(){
$scope.flags.loading = false;
});
Yes, this is sort of simplistic, but not all queries require a loading overlay (such as during pagination or refreshing). This is why we have opted not to simply use a decorator.
However, lets say you want to, I can think of a few ways of doing this. Lets say you're like us and keep your flags together in an object. Then you can use associations to your advantage:
MyService.flags = $scope.flags
... (inside the service) ...
this.flags.loading = true/false;
By establishing a reference as a property of the service, you can do all the state toggling from within the service, and avoid cluttering your controller. Again though, this might create the possible drawback of having 2 or more close-together queries conflicting (first query finishes and removes the loading state before the second one completes).
For this reason we have been find with setting the flag. We don't really check for 'loaded' we just check for data or use success callbacks.

Categories