I'm in the process of building out a fairly large Angular app and I've stuck to the design of building 'thin' controllers. My controllers don't try to do too much, they are each focused on one piece of functionality within my app.
There, however, is certain data that is 'shared' between controllers. I aim to avoid using $rootScope and instead rely on services to share data and 'state'.
When looking in the 'Network' tab of Chrome Dev Tools I notice certain services being called half a dozen times. So my question is, is this bad design? Are multiple calls to the same service within an Angular app not the 'Angular way' to do things? Note: these service calls take ~ 20ms each, so clearly not much of a performance hit...so far.
I'd suggest reducing the number of unnecessary HTTP requests that you're making for two reasons:
In production environments, these HTTP requests may take more time to complete when factors such as network latency or server load are taken into account; and
will delay the loading of any other assets such as images (assuming that they're coming from the same domain).
An approach that I've used when dealing with the scenario that you've described is to cache the response/data from the API in the service. So, on subsequent calls to the service, the data can be pulled from the cache rather than the API.
See a brief example below:
angular.module('app', ['ngResource'])
.factory('Post', ['$resource', function($resource) {
var posts = [];
var service = {
all: all
}
return service;
function all() {
// if cached posts exist, return those. Otherwise, make call to external API.
if (posts.length > 0) {
return posts.$promise;
} else {
posts = $resource('http://localhost/api/v1/posts.json').query();
return posts.$promise;
}
}
}]);
Note: you'll also have to consider resetting your cache however this would be dependent on your application logic.
Hope this helps!
L
In this case you should look to use $cacheFactory to reduce service calls.
Are you talking about REST services? Are you making $http calls in order to share data and state between controllers?
Why not use service/factory in Angular?
What you need is
1. DataCache service/factory - which will store your response from server
2. A directive - to call these services. include it in your different views
3. Now inside your service which is responsible for making http call first check if data is available in cache if yes return promise of stored data (you can use $q.when) if not make service call.
I have mentioned point 2 since I am assuming inside your various controller you must be doing something like AbcFactory.getItem().then() to avoid duplication of this code as you never know when the requirement will change since change is the only constant during development ;)
Related
I have a Torii adapter that is posting my e.g. Facebook and Twitter authorization tokens back to my API to establish sessions. In the open() method of my adapter, I'd like to know the name of the provider to write some logic around how to handle the different types of providers. For example:
// app/torii-adapters/application.js
export default Ember.Object.extend({
open(authorization) {
if (this.provider.name === 'facebook-connect') {
var provider = 'facebook';
// Facebook specific logic
var data = { ... };
}
else if (this.provider.name === 'twitter-oauth2') {
var provider = 'twitter';
// Twitter specific logic
var data = { ... };
}
else {
throw new Error(`Unable to handle unknown provider: ${this.provider.name}`);
}
return POST(`/api/auth/${provider}`, data);
}
}
But, of course, this.provider.name is not correct. Is there a way to get the name of the provider used from inside an adapter method? Thanks in advance.
UPDATE: I think there are a couple ways to do it. The first way would be to set the provider name in localStorage (or sessionStorage) before calling open(), and then use that value in the above logic. For example:
localStorage.setItem('providerName', 'facebook-connect');
this.get('session').open('facebook-connect');
// later ...
const providerName = localStorage.getItem('providerName');
if (providerName === 'facebook-connect') {
// ...
}
Another way is to create separate adapters for the different providers. There is code in Torii to look for e.g. app-name/torii-adapters/facebook-connect.js before falling back on app-name/torii-adapters/application.js. I'll put my provider-specific logic in separate files and that will do the trick. However, I have common logic for storing, fetching, and closing the session, so I'm not sure where to put that now.
UPDATE 2: Torii has trouble finding the different adapters under torii-adapters (e.g. facebook-connect.js, twitter-oauth2.js). I was attempting to create a parent class for all my adapters that would contain the common functionality. Back to the drawing board...
UPDATE 3: As #Brou points out, and as I learned talking to the Torii team, fetching and closing the session can be done—regardless of the provider—in a common application adapter (app-name/torii-adapters/application.js) file. If you need provider-specific session-opening logic, you can have multiple additional adapters (e.g. app-name/torii-adapters/facebook-oauth2.js) that may subclass the application adapter (or not).
Regarding the session lifecycle in Torii: https://github.com/Vestorly/torii/issues/219
Regarding the multiple adapters pattern: https://github.com/Vestorly/torii/issues/221
Regarding the new authenticatedRoute() DSL and auto-sesssion-fetching in Torii 0.6.0: https://github.com/Vestorly/torii/issues/222
UPDATE 4: I've written up my findings and solution on my personal web site. It encapsulates some of the ideas from my original post, from #brou, and other sources. Please let me know in the comments if you have any questions. Thank you.
I'm not an expert, but I've studied simple-auth and torii twice in the last weeks. First, I realized that I needed to level up on too many things at the same time, and ended up delaying my login feature. Today, I'm back on this work for a week.
My question is: What is your specific logic about?
I am also implementing provider-agnostic processing AND later common processing.
This is the process I start implementing:
User authentication.
Basically, calling torii default providers to get that OAuth2 token.
User info retrieval.
Getting canonical information from FB/GG/LI APIs, in order to create as few sessions as possible for a single user across different providers. This is thus API-agnotic.
➜ I'd then do: custom sub-providers calling this._super(), then doing this retrieval.
User session fetching or session updates via my API.
Using the previous canonical user info. This should then be the same for any provider.
➜ I'd then do: a single (application.js) torii adapter.
User session persistence against page refresh.
Theoretically, using simple-auth's session implementation is enough.
Maybe the only difference between our works is that I don't need any authorizer for the moment as my back-end is not yet secured (I still run local).
We can keep in touch about our respective progress: this is my week task, so don't hesitate!
I'm working with ember 1.13.
Hope it helped,
Enjoy coding! 8-)
EDIT
The short version:
Say I have application data is many different services. How do I get around needing to inject all of those services into every controller that displays application state?
EDIT
I am building my first Angular application. The basic design is I have a home page that shows the value of about 5 different variables (which are each pretty complicated). While on this page the app is collecting and analyzing data from bluetooth. Occasionally, the these 5 variables and some bluetooth data are saved to a REST back end and also saved to the device. There are pages for each of these 5 variables to change their value.
I have done my best to follow best practices. I have very thin controllers. I use services for all my data. I really only use $scope for binding data between views and controllers.
My issue now is that I started with a global "State" service to keep track of those 5 variables. I inject into any controller that needs to display state, and bind the html to it. Any time I want to change any state, I call a method of that State service to do it. This worked well, but now that State service is getting huge.
I have tried to break functions out to other services, but I run into the issue of needing to read data from the State service, then writing back to other properties of the State service. If I inject the other service into State, I can't inject State into the other service too.
I have thought about how I could have many smaller services, but I keep coming back to when I save the data to the server. When I do that I need to gather up data from every corner of the application to send up. If all this information is stored in different services, I am left with injecting all of them into a single service once again.
As I write this, I am pretty sure I am missing a big concept with using $scope across an application.
Any pointers would be appreciated,
Thanks,
Scott
Could you divide things into sub-services, and then make the State service an aggregator for these sub-services, then instead of injecting State into the sub-services, you inject the specific sub-service that you need? E.g.:
var app = angular.module('services', []);
app.service('sub1', function(){
return {
// ...
}
});
app.service('sub2', function(sub1){
var data = sub1.getData();
data.prop = 'new_value';
sub1.setData(data);
return {
// ...
}
});
app.service('State', function(sub1, sub2){
var data = sub1.getData();
data.prop = 'new_value';
sub1.setData(data);
var data = sub2.getData();
data.prop = 'new_value';
sub2.setData(data);
return {
// ...
}
});
Looks like you need Redux to help you manage your application state
https://github.com/wbuchwalter/ng-redux
In my AngularJS application, I have a Session service object that contains stuff like the current user, their preferences, the current company they belong to, and the current theme that they are using. Many places in my application refer to the Session service when they need to get at this data.
Because these variables are in a service, I cannot use scope watches to detect changes. So instead, I've decided to use the observer pattern. Various places in the application, such as other services, directives, controllers, etc. will register themselves with the Session service and provide a callback to be executed whenever the Session changes.
For example, if the user changes their theme, the <style> element in index.html that uses a custom directive will be notified, and it will recreate all of the overriding css rules for the new colors.
For another example, whenever the user's avatar is updated, the main menu bar controller will be notified to refresh and redraw the avatar. Stuff like this.
Obviously the data in Session has to be refreshed at least once before the various controllers, directives, etc. use it. The natural place to ask the Session service to get its session data was in a run block for the application-level module. This works pretty well, but I don't think it's the best place either.
One problem I have noticed is that when Firebug is open, the asynchronous nature of things loading causes ordering issues. For example, the directive that runs on the <style> element will run AFTER the Session service has refreshed in the application's run block... which means the theme will not get updated after pressing F5 because the callback is registered after the initialization of the data occured. I would have to call a manual refresh here to keep it in sync, but if I did that, it may execute twice in the times where the order is different! This is a big problem. I don't think this issue is just related to Firebug... it could happen under any circumstance, but Firebug seems to cause it somewhat consistently, and this is bad.
To recap... This asynchronous ordering is good:
Theme Directive registers callback to Session
Menu Bar application controller registers callback to Session
Session.refresh() is called in .run block.
This asynchronous ordering is bad:
Menu Bar application controller registers callback to Session
Session.refresh() is called in .run block.
Theme Directive registers callback to Session, but callback does not get executed since Session.refresh() was already executed.
So rather than use the observer pattern, or refresh the Session state via a run block, what the best way to design the services, etc. so that the session data will ALWAYS get refreshed after (or maybe before) the various other parts of the application require it? Is there some kind of event I can hook into that gets executed before directives and controllers are executed instead of the run block?
If my approach is generally sound, what can I add to it to really make it work the way it should?
Thanks!
In angular.js you have 2 way of using global variables:
use a $rootScope
use a service
Using $rootScope is very easy as you can simply inject it into any controller and change values in this scope. All global variables have problems!
Services is a singletons(What you need)!
I think in your case you can use
$rootScope
And
$scope.$watch
Great answer
Is there a reason you can't access the variables directly like this:
app.factory('SessionService', function() {
var items = {
"avatar": "some url"
};
return items;
});
var MainController = [$scope, 'SessionService', function($scope, SessionService){
$scope.session = SessionService;
$scope.modifyAvatar = function(url){
$scope.session.avatar = "some new url";
};
}];
var HeaderController = [$scope, 'SessionService', function($scope, SessionService){
$scope.session = SessionService;
// You probably wouldn't do this, you would just bind
// to {{session.avatar}} in your template
$scope.getAvatar = function(){
return $scope.session.avatar;
};
}];
Goodday Folks,
I have implemented a get or read operation using an AngularJS application to invoke a Isis service via an $http call. And displaying the collection on the screen using Angular ng-repeat. My next task is to do a create and update on the same entity using AngularJS. I am aware i have to send some parameters in the endpoint URL. Please, I need both Isis guidance and also importantly AngularJS hints or references or code.
I think i should get some sort of acknowledgment to confirm the create or update is succesful.
Below, is an extract from my code for the getList operation, just for starters.
Your logic might be totally different from this.
Thanks a lot.
sampleApp.controller('CrateUpdateController', function($scope, $http) {
$http({ method:'GET',
url: 'http://localhost:8080/xxx-webapp-1.0-SNAPSHOT/restful/services',
headers: {'Accept': 'application/json'}
}).
success(
function (data) {
//code to process outcome and acknowledgement etc
}
);
});
You might be best doing some Angular tutorials to learn the principles of that library. Then, (as I've mentioned before) take a look at using the Spiro library as the main interface to the RO server (Isis or RO.Net).
You should not mix so much different topics. A good practice is to distinguish the server side interface exposed by your HTTP server (Apache Isis) from the client side code accessing it.
This question should be break down to the following:
How to expose create and delete verbs for an existing resource in Isis
How to use create and delete verbs with Angular
The two topics are very different and it does not make so much sense to ask them together. The main point about having Isis exposing an HTTP interface is exactly to abstract from the client side logic.
As a side note, when accessing restful resources with Angular, try to use $resource instead of $http
I'm working in a team split into front-end and back-end developers. Sometimes, the front-end wants to make some REST requests to a http address / REST-resource that isn't yet implemented.
In ngMockE2E there's a $httpBackend service that seems quite useful for this. The question is, how do I mock only some specific urls/rest resources?
I want most requests like e.g. GET /users.json to go through to the backend server as usual. But while waiting for e.g. GET /users/tommy/profile.json to get implemented (by the back-end guys), it would be nice to have some "pretend response", aka mocking, so we can keep working. How should one go about?
Thanks :)
try include this in your project:
app.config(function($provide) {
$provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
}).run(function($httpBackend) {
// make sure we have access to
$httpBackend.whenGET('*').passThrough();
// here use your mocking data using $httpBackend
});