Very begginer question, sorry about that!
I understand how to store constants in services with AngularJS, for example:
.value('baseUrl', 'http://127.0.0.1:8001/api/v1/')
But how can I create another constant that uses another one?
It seems there is no DI in values ?!?
For example, If I want to create a GetUsersUrl which is baseUrlstring + 'Users/' (concatenation)
I guess it's simple...but unable to find how to do it.
Thank you.
You can store values in services, configure it in the run phase, and then access the values later. For example:
.factory('UrlService', function() {
var UrlService = {};
UrlService.baseUrl = undefined; // you can set a default value
UrlService.getUsersUrl = function() {
if (UrlService.baseUrl === undefined) {
return undefined;
} else {
return UrlService.baseUrl + '/Users/';
}
};
return UrlService;
});
The run phase happens after config.
.run(function(UrlService) {
UrlService.baseUrl = 'localhost:8001/api/v1';
});
Then, in your controllers, etc. you can inject UrlService and do
UrlService.getUsersUrl()
Edit: Rewrote the answer.
Edit 2: Another approach.
It also appears to me that you only really need baseUrl to be a constant. So, you could do:
.value('baseUrl', 'localhost:1337')
.factory('urlService', ['baseUrl', function(baseUrl) {
return {
getUsersUrl: function() { return baseUrl + '/users/'; },
// OR
usersUrl: baseUrl + '/users/' // it can also be a primitive value
}
}]);
This approach works (and is more "the Angular way") if you do not need to actually configure the baseUrl. For example, if you can fill in the appropriate value of the baseUrl based on which environment (dev, production, etc) is running, you wouldn't need to configure it. Or, if the value is constant.
Related
Right to it. I have a global variable which I would like to export, so I can use the value in following specs. But since protractor is not working synchronously, export happens before the variable gets updated to the right value.
The action is a click on button where player gets created and I need the username to be exported.
Console.log contains the right value and also export happens as it should, only thing, that it exports the hardcoded value or undefined if I set globalUsername = "";
Anyone can help on how to make this export sinchronized, so it will wait for all describes to finish up or the variable to get updated.
describe ("Quick add player", function() {
it ("New player is created and credentials are displayed", function() {
browser.ignoreSynchronization = true;
var playerData1 = playerData.getText().then(function(text) {
console.log("1: ", text);
return text.split("\n")[0];
//console.log(text.split(" ")[1]);
});
var globalUsername = "1234";
playerData1.then(function(text) {
globalUsername = text.split(" ")[1];
//expect(globalUsername).not.toEqual("");
console.log("*****************\n" + globalUsername);
});
});
});
module.exports = globalUsername;
Because you are using browser ignore synchronization, your tests will probably have nested then statements because you need to wait for promises to resolve. Instead of creating multiple then statements, you could put them into a single then statement. Also, you could tie your variable to the global namespace with global.username = username or add it to the global browser object with browser.username = username
describe ("Quick add player", function() {
it ("New player is created and credentials are displayed", function() {
browser.ignoreSynchronization = true;
global.username = "1234";
playerData.getText().then(function(text) {
console.log("1: ", text);
global.username = text.split("\n")[0].split(" ")[1];
console.log("*****************\n" + global.username);
});
});
});
// access using global.username tied to the global namespace
// instead of var username = require('thisfile');
So this solved my issue, where I export a string and not the variable....searched google with wrong tags.
This is placed outside the first describe at the bottom.
module.exports = {
exportUsername: function () {
return globalUsername;
}
};
In short my problem is this: I want to use angular constant functionality to save values that I will need in my app. I was wandering if one could build a property using the value of another property of the same constant. Like this:
app.constant("url", {
basicUrl: "/svc",
managementPanel: basicUrl + "/managemnent.html"
// and so on...
});
Is there any way one can achieve this? I tried using the "this" keyword but it referenced the window object.
You can put it all to function:
(function() {
var constant = {};
constant.base = 'base';
constant.nested = constant.base + '/nested';
constant.nested2 = constant.nested + '/nested2';
app.constant('test', constant)
})();
You will need to use a factory instead of the constant shorthand for this.
app.factory("url", function() {
var url = {};
url.basicUrl = "/svc";
url.managementPanel = url.basicUrl + "/managemnent.html";
return url;
})
I'm building an app, that is backed with node-mysql combo, and angularjs on the frontend part. The backend REST service is ready, but I'm struggling with modeling my relational data. There are some questions regarding this like : $resource relations in Angular.js or $resource relations in Angular.js [updated] . Are those approaches still the best approaches, or were there any significant changes in $resource ? Or maybe Restangular is the way to go?
Here is my technique:
I declare a factory called dataService, which is a wrapper around Restangular, extended with some other features.
First let me gave some code and then explain:
.factory('identityMap',
var identityMap = {};
return {
insert: function(className, object) {
if (object) {
var mappedObject;
if (identityMap[className]) {
mappedObject = identityMap[className][object.id];
if (mappedObject) {
extend(mappedObject, object);
} else {
identityMap[className][object.id] = object;
mappedObject = object;
}
} else {
identityMap[className] = {};
identityMap[className][object.id] = object;
mappedObject = object;
}
return mappedObject;
}
},
remove: function(className, object) {
if (identityMap[className] && identityMap[className][id]) delete identityMap[className][id];
},
get: function(className, id) {
return identityMap[className] && identityMap[className][id] ? identityMap[className][id] : null;
},
flush: function(){
identityMap = {};
}
};
}
.factory('modelService', ['Restangular', 'identityMap', '$rootScope', '$log', function(Restangular, identityMap, $rootScope, $log) {
var ENUM1 = {STATE:0, OTHER_STATE:1, OTHER_STATE2: 2},
ENUM2 = {OK:0, ERROR:1, UNKNOWN:2};
function extendModel(obj, modelExtension, modelName){
angular.extend(obj, modelExtension);
obj.initExtension();
obj = identityMap.insert(modelName, obj);
}
function broadcastRestEvent(resourceName, operation, data){
$rootScope.$broadcast(resourceName + $filter('capitalize')(operation), data);
}
var resource1Extension = {
_extensionFunction1: function() {
// ... do something internally ...
if (this.something){
// this.newValue ....
;
}
else {
// ....;
}
},
publicExtensionFunction: function(param1) {
// return something
},
initExtension: function() {
this._extensionFunction2();
extendModel(this.resource2, resource2Extension, 'resource2');
}
};
var resorce2Extension = {
_extensionFunction1: function() {
// do something internally
},
publicExtensionFunction = function(param1) {
// return something
},
initExtension: function(){
this._extensionFunction1;
}
};
var modelExtensions = {
'resource1': resource1Extension,
'resource2': resorce2Extension
};
var rest = Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('/api');
RestangularConfigurer.setOnElemRestangularized(function(obj, isCollection, what, Restangular){
if (!isCollection) {
if (modelExtensions.hasOwnProperty(what)) {
extendModel(obj, modelExtensions[what], what);
}
else {
identityMap.insert(what, obj);
}
if (obj.metadata && obj.metadata.operation) {
broadcastRestEvent(what, obj.metadata.operation, obj);
}
}
return obj;
});
RestangularConfigurer.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
var newData;
if (operation === 'getList') {
newData = data.objects;
newData.metadata = {
numResults: data.num_results,
page: data.page,
totalPages: data.total_pages,
operation: operation
};
data = newData;
}
else if (operation === 'remove') {
var splittedUrl = url.split('/');
var id = splittedUrl.pop();
var resource = splittedUrl.pop();
identityMap.remove(resource, id);
broadcastRestEvent(resource, operation, id);
}
else {
data.metadata = {operation: operation};
}
return data;
});
});
return {
rest: rest,
enums: {
ENUM1: ENUM1,
ENUM2: ENUM2
},
flush: identityMap.flush,
get: identityMap.get
}
}]);
1) Let me explain identityMap (it's the code from this blog post with some extended features):
Let's consider a REST model which looks like this (each resource represents a database table):
resource 1:
id = Integer
field1 = String
field2 = String
resource2s = [] (List of resources2 which points to this resource with their foreign key)
resource 2:
id = Integer
field1 = String
field2 = String
...
resource1_idfk = Foreign Key to resource 1
Resource API is so smart that it returns resource1 relationships with resources2 with GET /api/resource1/1 to save the overhead that you would get with GET to resource2 with some query parameters to resource1_idfk...
The problem is that if your app is doing the GET to resource1 and then somewhere later GET to resource2 and edits the resource2, the object representing the resource2 which is nested in resource1 would not know about the change (because it is not the same Javascript object reference)
The identity map solves this issue, so you hold only one reference to each resource's instance
So, for example, when you are doing an update in your controller the values automatically updates in the other object where this resource is nested
The drawback is that you have to do memory management yourself and flush the identity map content when you no longer need it. I personally use Angular Router UI, and define this in a controller which is the root of other nested states:
$scope.$on("$destroy", function() {
modelService.flush();
});
The other approach I use within the Angular Router UI is that I give the id of the resource which i want to edit/delete within that controller as the parameter of nested state and within the nested state i use:
$scope.resource1instance = modelService.get('resource1', $stateParams.id);
You can than use
resource1.put(...).then(
function(){
// you don't need to edit resource1 in list of resources1
$state.go('^');
}
function(error){
handleError(error);
});
2) When I need to use some new functionality over resources I use `Restangular's setOnElemRestangularized. I think the code above is self explanatory and very similar to the one mentioned in blog post I have mentioned above. My approach is slightly different from the one in that post, that I don't use the mixin initialization before, but after I mix it to the object, so one could reference the new functions in initializer. The other thing I don't use, for example, he creates single factory for every resource, for example Proposal for extended logic and the other factory ProposalSvc for manipulating the instances. For me that's a lot of code you don't have to write and personally I think that Javascript is not suited very well for this object oriented approach, so I return just the whole Restangular object and do operations with it.
3) Another thing I have there is the broadcast of events when something in my model changes with Restangular, this is something I needed when I used ng-table. For example, when the model changed and rows in my table needed to be updated to reference the changes, so in the controller which manages the table I use $scope.on('eventName') and then change appropriate row. These events are also great when you have a multiuser live application and you use websockets for server notifications (code not included here in modelService). For example somebody deletes something in a database, so the server sends a notification to everyone who is alive through websocket about the change, you then broadcast the same event as used in Restangular and the controller does the same edits on its data.
This blog post should help you make your choice http://sauceio.com/index.php/2014/07/angularjs-data-models-http-vs-resource-vs-restangular/
I agree that there are a lot of good practices using http headers in Restangular, but you can pick them in the source and use them directly.
What you have to wonder is, will you be able to wrap your nested resources within a $resource and make instance calls while modifying the parent object. And that's not a given.
Your question seems to be asking whether you should be using ngResource, Restangular or some other framework or drop down to the low-level and use $http directly.
$resource is still widely used because it's included in the official docs and in all the popular tutorials and articles but Restangular is fairly popular.
The website ngModules shows a listing of REST API modules for AngularJS.
If you have a simple REST API, go with $resource for now and then switch to Restangular if you're doing lots of custom coding and filtering. It is a much nicer framework and more extensible.
First I'd like to say my appreciation for this great website that I rely on rather often but never have used to ask anything.
I'm currently learning AngularJS by reading "Mastering web application development with AngularJS" and going through the provided examples.
I would like to understand how I can switch between different implementations of a provider (service) with minimal code change.
Here is the "notifications" service that I need to configure with different implementations of an "archiver" service, code for both below :
angular.module('notificationsApp', ['archiver'])
.provider('notificationsService', function () {
var notifications = [];
return {
$get : function(archiverService) {
return {
push:function (notification) {
var notificationToArchive;
var newLen = notifications.unshift(notification);
if (newLen > 5) {
notificationToArchive = notifications.pop();
archiverService.archive(notificationToArchive);
}
}
};
}
};
})
.config(function(notificationsServiceProvider){
**How could I make the 'archiverService' be of superConsoleArchiverService or consoleArchiverService ?**
});
I would like to be able to choose between different implementations for my "archiverService", namely "superConsoleArchiverService" or "consoleArchiverService" as defined in the following module.
angular.module('archiver', [])
.provider('consoleArchiverService', function () {
return {
$get : function() {
return {
archive:function (archivedNotification) {
console.log(archivedNotification);
}
};
}
};
})
.provider('superConsoleArchiverService', function () {
return {
$get : function() {
return {
archive:function (archivedNotification) {
console.log('super ' + archivedNotification);
}
};
}
};
});
Thanks a lot for helping me through this !
(also, I hope this question makes sense and has not been answered a gazillion times)
Let's say you have some condition, say a variable to use_super.
Then you could do something like this, by injecting $provide, and both of your providers:
$provide.value('archiver', use_super ? superConsoleArchiverService : consoleArchiverService);
Hope this helped!
Thanks to the answer provided by hassassin I was able to make it work, following is some working version, no code was changed in the 'archiver' module.
angular.module('notificationsApp', ['archiver'])
.provider('notificationsService', function () {
var notifications = [];
return {
$get : function(configuredArchiver) {
return {
push:function (notification) {
var notificationToArchive;
var newLen = notifications.unshift(notification);
if (newLen > 5) {
notificationToArchive = notifications.pop();
configuredArchiver.archive(notificationToArchive);
}
}
};
}
};
})
.config(function(notificationsServiceProvider, superConsoleArchiverServiceProvider, consoleArchiverServiceProvider, $provide){
// Here it is possible to set the 'configuredArchiver to either one of my archivers
//$provide.provider('configuredArchiver',consoleArchiverServiceProvider);
$provide.provider('configuredArchiver',superConsoleArchiverServiceProvider);
});
Some things I still don't fully understand like why can't I inject the 'configuredArchiver' directly in the 'notificationService' provider, but I strongly suspect it is related to my still very small grasp on the life cycle of AngularJS objects. Back to reading !
After upgrading to 1.2, promises returned by my services behave differently...
Simple service myDates:
getDates: function () {
var deferred = $q.defer();
$http.get(aGoodURL).
success(function (data, status, headers, config) {
deferred.resolve(data); // we get to here fine.
})......
In earlier version I could just do, in my controller:
$scope.theDates = myDates.getDates();
and the promises returned from getDates could be bound directly to a Select element.
Now this doesn't work and I'm forced to supply a callback on the promise in my controller or the data wont bind:
$scope.theDates = matchDates.getDates();
$scope.theDates.then(function (data) {
$scope.theDates = data; // this wasn't necessary in the past
The docs still say:
$q promises are recognized by the templating engine in angular, which means that in templates you can treat promises attached to a scope as if they were the resulting values.
They (promises) were working in older versions of Angular but in the 1.2 RC3 automatic binding fails in all my simple services.... any ideas on what I might be doing wrong.
There are changes in 1.2.0-rc3, including one you mentioned:
AngularJS 1.2.0-rc3 ferocious-twitch fixes a number of high priority
issues in $compile and $animate and paves the way for 1.2.
This release also introduces some important breaking changes that in some cases could break your directives and templates. Please
be sure to read the changelog to understand these changes and learn
how to migrate your code if needed.
For full details in this release, see the changelog.
There is description in change log:
$parse:
due to 5dc35b52, $parse and templates in general will no longer automatically unwrap promises. This feature has been deprecated and
if absolutely needed, it can be reenabled during transitional period
via $parseProvider.unwrapPromises(true) api.
due to b6a37d11, feature added in rc.2 that unwraps return values from functions if the values are promises (if promise unwrapping is
enabled - see previous point), was reverted due to breaking a popular
usage pattern.
As #Nenad notices, promises are no longer automatically dereferenced. This is one of the most bizarre decisions I've ever seen since it silently removes a function that I relied on (and that was one of the unique selling points of angular for me, less is more). So it took me quite a bit of time to figure this out. Especially since the $resource framework still seems to work fine. On top of this all, this is also a release candidate. If they really had to deprecate this (the arguments sound very feeble) they could at least have given a grace period where there were warnings before they silently shut it off. Though usually very impressed with angular, this is a big minus. I would not be surprised if this actually will be reverted, though there seems to be relatively little outcry so far.
Anyway. What are the solutions?
Always use then(), and assign the $scope in the then method
function Ctrl($scope) {
foo().then( function(d) { $scope.d = d; });
)
call the value through an unwrap function. This function returns a field in the promise and sets this field through the then method. It will therefore be undefined as long as the promise is not resolved.
$rootScope.unwrap = function (v) {
if (v && v.then) {
var p = v;
if (!('$$v' in v)) {
p.$$v = undefined;
p.then(function(val) { p.$$v = val; });
}
v = v.$$v;
}
return v;
};
You can now call it:
Hello {{ unwrap(world) }}.
This is from http://plnkr.co/edit/Fn7z3g?p=preview which does not have a name associated with it.
Set $parseProvider.unwrapPromises(true) and live with the messages, which you could turn off with $parseProvider.logPromiseWarnings(false) but it is better to be aware that they might remove the functionality in a following release.
Sigh, 40 years Smalltalk had the become message that allowed you to switch object references. Promises as they could have been ...
UPDATE:
After changing my application I found a general pattern that worked quite well.
Assuming I need object 'x' and there is some way to get this object remotely. I will then first check a cache for 'x'. If there is an object, I return it. If no such object exists, I create an actual empty object. Unfortunately, this requires you to know if this is will be an Array or a hash/object. I put this object in the cache so future calls can use it. I then start the remote call and on the callback I copy the data obtained from the remote system in the created object. The cache ensures that repeated calls to the get method are not creating lots of remote calls for the same object.
function getX() {
var x = cache.get('x');
if ( x == undefined) {
cache.put('x', x={});
remote.getX().then( function(d) { angular.copy(d,x); } );
}
return x;
}
Yet another alternative is to provide the get method with the destination of the object:
function getX(scope,name) {
remote.getX().then( function(d) {
scope[name] = d;
} );
}
You could always create a Common angular service and put an unwrap method in there that sort of recreates how the old promises worked. Here is an example method:
var shared = angular.module("shared");
shared.service("Common", [
function () {
// [Unwrap] will return a value to the scope which is automatially updated. For example,
// you can pass the second argument an ng-resource call or promise, and when the result comes back
// it will update the first argument. You can also pass a function that returns an ng-resource or
// promise and it will extend the first argument to contain a new "load()" method which can make the
// call again. The first argument should either be an object (like {}) or an array (like []) based on
// the expected return value of the promise.
// Usage: $scope.reminders = Common.unwrap([], Reminders.query().$promise);
// Usage: $scope.reminders = Common.unwrap([], Reminders.query());
// Usage: $scope.reminders = Common.unwrap([], function() { return Reminders.query(); });
// Usage: $scope.reminders.load();
this.unwrap = function(result, func) {
if (!result || !func) return result;
var then = function(promise) {
//see if they sent a resource
if ('$promise' in promise) {
promise.$promise.then(update);
}
//see if they sent a promise directly
else if ('then' in promise) {
promise.then(update);
}
};
var update = function(data) {
if ($.isArray(result)) {
//clear result list
result.length = 0;
//populate result list with data
$.each(data, function(i, item) {
result.push(item);
});
} else {
//clear result object
for (var prop in result) {
if (prop !== 'load') delete result[prop];
}
//deep populate result object from data
$.extend(true, result, data);
}
};
//see if they sent a function that returns a promise, or a promise itself
if ($.isFunction(func)) {
// create load event for reuse
result.load = function() {
then(func());
};
result.load();
} else {
then(func);
}
return result;
};
}
]);
This basically works how the old promises did and auto-resolves. However, if the second argument is a function it has the added benefit of adding a ".load()" method which can reload the value into the scope.
angular.module('site').controller("homeController", function(Common) {
$scope.reminders = Common.unwrap([], Reminders.query().$promise);
$scope.reminders = Common.unwrap([], Reminders.query());
$scope.reminders = Common.unwrap([], function() { return Reminders.query(); });
function refresh() {
$scope.reminders.load();
}
});
These were some good answers, and helped me find my issue when I upgraded angular and my auto-unwrapping of promises stopped working.
At the risk of being redundant with Peter Kriens, I have found this pattern to work for me (this is a simple example of simply putting a number of famous people's quotes onto a page).
My Controller:
angular.module('myModuleName').controller('welcomeController',
function ($scope, myDataServiceUsingResourceOrHttp) {
myDataServiceUsingResourceOrHttp.getQuotes(3).then(function (quotes) { $scope.quotes = quotes; });
}
);
My Page:
...
<div class="main-content" ng-controller="welcomeController">
...
<div class="widget-main">
<div class="row" ng-repeat="quote in quotes">
<div class="col-xs-12">
<blockquote class="pull-right">
<p>{{quote.text}}</p>
<small>{{quote.source}}</small>
</blockquote>
</div>
</div>
</div>
...