I made a request for a php getting a JSON string, when I access the string in my template using $ctrl.respostas[0].status it returns a string with a value, but when I try to use the same model in my controller (this.respostas[0].status), it came nothing, I need to check the answer in my controller, but I don't know how, any one can help me?
Template:
<p>respostas: {{$ctrl.response}}</p>
<p>confere: {{$ctrl.test}}</p>
<p>status: {{$ctrl.response[0].status}}</p>
Controller
angular.
module('tela').
component('tela', {
templateUrl: 'tela/tela.template.html',
controller: ['Request',
function telaController(Request, ̶$̶r̶o̶o̶t̶S̶c̶o̶p̶e̶) {
this.test = "ola";
this.id = "1";
this.validate = function validate() {
this.response= Request.save({id: this.id},[]);
this.test = "algo";
this.test = this.response[0].status;
}
}
]
});
Request
angular.
module('request.Request').
factory('Request', ['$resource',
function($resource) {
return $resource('./php/acessing.php',{
teste: "#id",
}, {
save: {
method: 'POST',
hasBody: true,
isArray: true,
cache: false
}
});
}
]);
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data.
The HTML template shows the data because the interpolation bindings {{ }} check the object every digest cycle and update the DOM. The controller on the other hand needs to use the attached promise to examine the data:
app.component('tela', {
templateUrl: 'tela/tela.template.html',
controller: ['Request',
function telaController(Request, ̶$̶r̶o̶o̶t̶S̶c̶o̶p̶e̶) {
this.test = "ola";
this.id = "1";
this.validate = function validate() {
this.response= Request.save({id: this.id},[]);
var promise = this.response.$promise;
promise.then(data => {
console.log(data[0].status);
console.log(this.response[0].status);
});
}
}
]
});
From the Docs:
The Resource instances and collections have these additional properties:
$promise: The promise of the original server interaction that created this instance or collection.
On success, the promise is resolved with the same resource instance or collection object, updated with data from server.
On failure, the promise is rejected with the http response object.
— AngularJS $resource API Reference - Usage
Related
I have the following angular service. It retrieves a array of json messages from the backend and I want to be able to convert those messages (plain js objects with no behavior) into object of type Message (see below).
Ideally I'd like to transform the data from the unresolved ng promise and passing each json message into the Message constructor as follows:
new Message(jsonMsg);
How can I achieve that?
Here is my service:
function Message(data) {
var defaults = {
sender: null,
recipient: null,
messageRead: false
};
angular.extend(this, defaults, data);
}
Message.prototype.getCounterparty = function (user) {
if (!this.sender) return null;
return (user.id !== this.sender.id) ? this.sender : this.recipient;
};
Message.prototype.isSender = function (user) {
return user.id === this.sender.id;
};
Message.prototype.isRecipient = function (user) {
return user.id === this.recipient.id;
};
...
findLatestsMessages: function (otherId) {
return $http.get('/api/message/find-latest-messages' + otherId);
}
You have to use a factory :
angular.factory('Message', [function() {
var Message= function(data) {
var defaults = {
sender: null,
recipient: null,
messageRead: false
};
angular.extend(this, defaults, data);
}
Message.prototype.isSender = function (user) {
return user.id === this.sender.id;
};
// ...
return Message;
}]);
Then you inject your factory in your controller
// ...
angular.controller('ctrl', ['$http', '$scope', 'Message', function($http, $scope, Message) {
$scope.messages = []
$scope.findLatestsMessages = function (otherId) {
return $http
.get('/api/message/find-latest-messages' + otherId)
.then(function(response) {
// and you create your messages when your promise has resolved
$scope.messages = response.data.map(function(m) {
return new Message(m);
}
});
}]);
// Assuming json response looks like
// [
// {sender: "John Doe", recipient: "Jane Doe", messageRead: true},
// {sender: "John Doe", recipient: "Jane Doe", messageRead: true}
// ]
findLatestsMessages: function (otherId) {
return $http
.get('/api/message/find-latest-messages' + otherId)
.then(function (response) {
var messages = response.data;
return messages.map(function (message) {
return new Message(message);
});
});
}
USAGE:
findLatestsMessages(someId).then(function (messages) {
// messages is an array of Message objects.
});
Then I will no longer have a promise to deal with on the calling-side?
You can return an object reference that later gets resolved to what you want. But beware, an XHR created by $http always resolves asynchronously, sometime after it is created.
As example:
function MessageResponseObject(url) {
var message = {};
var httpPromise = http.get(url);
//transform response
var derivedPromise = httpPromise.then( function(response) {
//save data to object
message.data = response.data;
//return message reference for chaining
return message;
});
//attach promise to object
message.$promise = derivedPromise;
//
return message;
});
$scope.message = MessageResponseObject(url);
You can use the response object in your template without a promise.
<div ng-repeat="item in message.data">
{{item}}
</div>
This will work in the UI because the AngularJS framework puts a $watchCollection on message.data. It checks the value after each digest cycle; and when the data gets resolved, it places the data on the DOM.
Clients in the controller that need to wait can use the attached promise.
$scope.message.$promise.then (function onFulfilledHandler(message) {
//do subsequent actions
}).catch (function onRejected(errorResponse) {
//do error actions
});
It is important to realize that using method immediately returns an empty reference. Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. Having an empty object results in no rendering, once the data arrives from the server then the object is populated with the data and the view automatically re-renders itself showing the new data. This means that in most cases one never has to write a callback function for the action methods.1
Best practice
Return the promise as a property of the object that resolves to the object. This makes it easy to use in resolve section of $routeProvider.when() to defer view rendering until the resource(s) are loaded.
For more information on this methodology, see AngularJS ngResource $resource API Reference.
Is it possible to add values to my $resource $cacheFactory from my controller & keep this data in sync across multiple controllers? Essentially what I'm trying to accomplish is:
1) pull JSON resource from API
2) manipulate JSON as if it weren't a $resource anymore, just a plain JSON object that I can use between controllers.
Is there an "angular way" to do this or should I just cache the whole place list using local storage & read and write everything else from there?
.factory('places', ['$resource','environment','$cacheFactory',
function($resource, environment, $cacheFactory) {
var cache = $cacheFactory('places');
return $resource(environment.apis.places, {}, {
query: {
isArray:true,
method: 'GET',
cache: cache
}
});
}
])
.controller('ItemCtrl', function($scope, places) {
places.query({}, function(result){
$scope.item = result[0]
})
$scope.makeFav = function(index){
//add a new key to cached places data
$scope.item.fav = true
}
}
.controller('ListCtrl', function($scope, places) {
places.query({}, function(result){
$scope.item = result //should get the updated cache changed by ItemCtrl
})
console.log($scope.item[0].fav) //should = true
}
Use the following process:
Create a constant recipe
Inject cacheFactory
Create an instance of cacheFactory
Inject the constant into each controller
Reference the instance of cacheFactory
Manipulate the instance of cacheFactory in each controller
function sharedCache($cacheFactory)
{
var sharedCache = $cacheFactory.get('sharedCache') ? $cacheFactory.get('sharedCache') : $cacheFactory('sharedCache');
return sharedCache;
}
function bar(sharedCache, $scope)
{
sharedCache.put('config', $scope.config);
}
bar.$inject = ['sharedCache', '$scope'];
sharedCache.$inject = ['$cacheFactory'];
angular.module('foo',[]);
angular.module('foo').constant('sharedCache', sharedCache);
angular.module('foo').controller('bar', bar);
References
AngularJS Documentation for iid
ng-book: caching through HTTP
I have a service which wraps a resource. I have had to refactor it to take a param (websiteId) which is gotten from an async call. I thought I could just wrap the resource inside the resource, but I am getting a standard injection error.
What is the correct way to have a service which wraps a resource, use a param which comes from a promise?
searchApp.factory('MyTestService', ['$resource', 'WebsiteService', 'appConfig', function ($resource, WebsiteService, appConfig) {
WebsiteService.getCurrentWebsiteId().then(function(websiteId){
return $resource(appConfig.apiBaseUrl + 'tests/:id/?website=:websiteId', {
websiteId: websiteId
});
});
}]);
I will try this approach:
searchApp.factory( 'MyTestService', [ '$resource', 'WebsiteService', 'appConfig', '$q' function ( $resource, WebsiteService, appConfig, $q ) {
var getService = function() {
var defer = $q.defer();
WebsiteService.getCurrentWebsiteId( function( websiteId ) {
defer.resolve({
websiteId : websiteId,
resource : $resource( appConfig.apiBaseUrl + 'tests/:id/?website=:websiteId', {
websiteId: '#websiteId'
})
});
});
return defer.promise;
};
return getService;
}]);
Main differences are:
Returning a promise from your service.
Parameter value prefixed with an # -> then the value for that
parameter will be extracted from the corresponding property on the
data object (provided when calling an action method). For example, if
the defaultParam object is {someParam: '#someProp'} then the value of
someParam will be data.someProp.
EDIT
How to use it
Since this service is returning a promise which value is a resource object, to use it you first have to resolve the service promise and then call the appropiate resource class object (Im using the same terminology than Angular documentation). Once you have your resource class object, you can request your resource and resolve it.
The correct usage will be:
MyTestService.then( function( responseObject ) {
responseObject.resource.get( { websiteId : responseObject.websiteId }, function( response ) {
console.log( response );
})
});
Another different history is why will you need this.
If I were you, I will create different resources and then just nest promises, in order to resolve one before getting the other.
But thats was not your question.
I have the following pattern in my AngularJS which calls for refactoring:
$scope.advertisers = Advertiser.query()
$scope.advertisersMap = {};
$scope.advertiser.$then(function (response) {
arrayToMap(response.resource, $scope.advertisersMap)
}
arrayToMap is function that adds each item in an array to the object with it's ID as key.
Now, I would have liked that this will happen in Advertiser itself, e.g.
$scope.allAdvertisers = Advertiser.query()
// Somewhere else
var advertiser = Advertiser.get({id: 2})
Where Advertiser.get({id: 2}) will return from a cache populated earlier by the query method.
Advertiser is defined in factory:
.factory('Advertiser', ['djResource', 'Group', function ($djResource, Group) {
var Advertiser = $djResource('/campaigns/advertisers/:advertiserId/', {advertiserId: '#id'});
Advertiser.prototype.getGroups = function () {
return Group.getByAdvertiser({advertiserId: this.id});
};
return Advertiser;
}])
Sadly, DjangoRestResource (and $resource which it wraps) caches by URLs, so query() will cache /advertisers/ while get(2) will cache /advertisers/2/, but I want query to cache in such way that get will be able to retrieve it as well.
I've tried replacing the query function by a wrapper function that does the caching, but I want it to return an promise which is also an Array like $resource does. It was something like:
var oldQuery = Advertiser.query;
var cache = $cacheFactory('advertisers');
Advertiser.query = function () {
var promise = oldQuery();
return promise.then(function (response) {
angular.forEach(response.resource, function (resource) {
cache.put(resource.id, resource)
})
})
};
But then the returned promise is no longer an Array-like object, and it doesn't not encapsulate the returned results in an Advertiser object as it used to, which breaks most of my code expects that Advertiser.query() will eventually be an Array.
What other approaches should I try? This snippet repeats itself in every controller for multiple factories and it hurts my eyes.
Here is solution to the problem:
function makeCachingMethod(object, method, cache, config) {
var $q = angular.injector(['services']).get('$q');
var oldMethod = object[method];
object[method] = function () {
var result;
if (!config.isArray) {
var id = config.idParam ? arguments[0][config.idParam] : arguments[0];
result = cache.get(id);
if (result !== undefined) {
if (result.$promise === undefined) {
result.$promise = $q.when(result);
result.$resolved = true;
}
return result;
}
}
result = oldMethod.apply(this, arguments);
result.$promise.then(function (data) {
if (config.isArray) {
angular.forEach(data, function (item) {
cache.put(item.id, item);
})
} else {
cache.put(data.id, data);
}
return data;
});
return result;
}
}
And an example usage:
app.factory('Country', ['$resource', '$cacheFactory', function ($resource, $cacheFactory) {
var Country = $resource('/campaigns/countries/:id', {id: "#id"}, {
query: {method: 'GET', url: '/campaigns/countries/', isArray: true},
get: {method: 'GET', url: '/campaigns/countries/:id/', isArray: false}
});
var cache = $cacheFactory('countries');
makeCachingMethod(Country, 'query', cache, {isArray: true});
makeCachingMethod(Country, 'get', cache, {idParam: 'id'});
return Country;
}])
What is happening here?
I use makeCachingMethod to decorate the original method created by $resource. Following the pattern used by $resource itself, I use a configuration object to signal whether the decorated method returns an array or not, on how the id is passed in queries. I assume, though, that the key of the ID to save is 'id', which is correct for my models but might need to be changed.
Noticed that before returning an object from the cache, the decorator adds to it $promise and $resolved attributes, since my application expects objects originated from $resource which have these properties, and in order to keep using the promises API, e.g.:
$scope.advertiser = Advertiser.get({advertiserId: $scope.advertiserId});
$scope.advertiser.$promise.then(function () {
$scope.doSomething();
});
Notice that since the function is defined outside the scope of any Angular module it is required to inject the $q service using angular.injector(). A nicer solution will be to return a service for invoking the decorator function. Such service could also handle the generation of the caches themselves.
This solution does not handle the expiration of cached models, which isn't much of problem in my scenario, as these rarely change.
I'm not aware of any cache support directly with $resource, you could create a factory that abstracts the Advertiser resource and handle the caching yourself.
Here is some helpful info on caching:
https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#validating-cached-responses-with-etags
I'm working on an app at the minute using $resource and every method is abstracting so I can check the cache to see if what I'm requesting is there and return it, if not, make the call using $resource.
I have a pretty standard app which will display news items from a remote JSON feed. So basically I have decided to poll the remote server and store the JSON in localStorage (to enable offline usage). For the moment, I have a manual page/view I must click on to update the localStorage , this works fine.
The problem is that after I use my temporary manual update page, I then go to the news page/view and it is not updated. To view the current JSON contents I must hit refresh (while still developing in the browser.)
I'm totally new to Angular and have tried to find solutions to this myself - $watch or reload: true seem to be suggested as fixes, but I cannot get them to work in my case.
Route
.state('tab.news', {
url: '/news',
reload: true,
views: {
'news-tab': {
templateUrl: 'templates/news_home.html',
controller: 'newsCtrl'
}
}
})
factory
angular.module('schoolApp.services', [])
.factory('newsService', function($q) {
var newsHeadlines =localStorage.getItem('newsHeadlines') || '{"status":"READFAIL"}'; // get news as a JSON string. if newsHeadlines not found return a JSON string with fail status
var newsHeadlinesObj = JSON.parse(newsHeadlines);// convert to an object
console.log("factory newsService ran");
return {
findAll: function() {
var deferred = $q.defer();
deferred.resolve(newsHeadlinesObj);
return deferred.promise; // or reject(reason) to throw an error in the controller https://docs.angularjs.org/api/ng/service/$q
},
findById: function(newsId) {
var deferred = $q.defer();
var newsItem = newsHeadlinesObj[newsId];
deferred.resolve(newsItem);
return deferred.promise;
}
}
});
Controller
schoolApp.controller('newsCtrl', function($scope, newsService) {
console.log ( 'newsCtrl ran' );
newsService.findAll().then(function (newsHeadlinesObj) {
$scope.newsHeadlinesObj = newsHeadlinesObj;
}, function(error){
console.log(error)
});
})
Looking at my console, the first time I read the news, the factory then controller run, but if I go to pull more data down, then go hack to news, only the controller runs, unless I refresh, then both run again.
I do not need the news view to update 'live' while still on it (but if that can be easilly done all the better) - just to pick up new data when you go back to news after being elsewhere in the app.
Thank you.
Factories return singletons and only run once. The object newsService is cached by angular. The var declarations for newsHeadlines and newsHeadlinesObj will only ever run once; meaning your promise returning methods will always resolve the promise with the same data that was retrieved when your factory was first instantiated. You should put them in a function and call it from your find methods on the singleton object.
.factory('newsService', function($q) {
function getHeadlines() {
var newsHeadlines = localStorage.getItem('newsHeadlines') || '{"status":"READFAIL"}'; // get news as a JSON string. if newsHeadlines not found return a JSON string with fail
return JSON.parse(newsHeadlines);// convert to an object
}
return {
findAll: function() {
var headlines = getHeadlines();
var deferred = $q.defer();
deferred.resolve(headlines);
return deferred.promise; // or reject(reason) to throw an error in the controller https://docs.angularjs.org/api/ng/service/$q
},
findById: function(newsId) {
var headlines = getHeadlines();
var deferred = $q.defer();
var newsItem = headlines[newsId];
deferred.resolve(newsItem);
return deferred.promise;
}
}
});
PS - I'm sure you know and are planning to do things differently later or something, but just in case you don't: Using promises here is pointless and you have no need for $q here. You could simply return the data instead of returning the promises.
I solved this withouut promises, I just used $rootScope in the factory and $scope.$on in the controller; when I change the factory, i use $rootScope.$broadcast to tell the controller that I change it.
.factory('dataFactory', ['$http', '$rootScope', function ($http, $rootScope) {
var dataFactory = {
stock: null,
getStock: getStock
}
function getStock() {
$http.get("/api/itemfarmacia/").then(function success(res) {
dataFactory.stock = res.data;
$rootScope.$broadcast('changingStock'); //Ones who listen this will see it
}, function error(err) {
console.log("Bad request");
})
}
return dataFactory;
}])
and in the controller
.controller('atencion', ["$scope", "$state", "dataFactory", function ($scope, $state, dataFactory) {
$scope.stock = dataFactory.stock; //At first is null
dataFactory.getStock(); //wherever you execute this, $scope.stock will change
$scope.$on('changingStock', function () {//Listening
$scope.stock = dataFactory.stock; //Updating $scope
})
}])