Simple problem with hopefully simple enough solution.
I have defined multiple services to perform CRUD operations with tags.
myApp.factory('GetTags', ['$resource', function ($resource) {
return $resource('/myApp/API/Service/GetTagList', {}, {
query: { method: 'GET', params: { groupId: 'groupId' }, }, isArray: true,
});
}]);
myApp.factory('GetTag', ['$resource', function ($resource) {
return $resource('/myApp/API/Service/GetTag', {}, {
query: { method: 'GET', params: { tagId: 'tagId' }, }, isArray: true,
});
}]);
myApp.factory('SaveTag', ['$resource', function ($resource) {
return $resource('/myApp/API/Service/CreateTag', {}, {
query: { method: 'POST', params: {/*createObj*/}, }, isArray: true,
});
}]);
myApp.factory('UpdateTag', ['$resource', function ($resource) {
return $resource('/myApp/API/Service/UpdateTag', {}, {
query: { method: 'POST', params: {/*updateObj*/}, }, isArray: true,
});
}]);
Later on in my controller I want to do something like this in my tags function:
myApp.controller('myCtrl', ['$scope', '$routeParams', 'GetTags', 'GetTag', 'SaveTag', function ($scope, $routeParams, GetTags, GetTag, SaveTag) {
...
// the goal of this function to keep a copy of
// tags collection in client memory that mimics database
// as well as adding selected tags to forms object
// eg: myForm = {... Tags: [], ...}
$scope.addtag = function (tag, subTag){
...
if (!($scope.tags.length > 0)) {
// skip checking and just add tag
SaveTag.save({ Name: tag, Desc: "", ParentId: null, GroupId: 12, }, function (data) {
console.log('save tag: ', data);
//todo: should wait for data of save operation to comeback
// before moving on to requesting a full object
GetTag.get({ tagId: data.Id, groupId: 12, }, function (data) {
console.log(' get tag: ', data);
tagObj = data.tag;
});
});
// push newly created tag into tags collection
$scope.tags.push(tagObj);
...
};
...
});
I know a skipped a lot of details from my controller and function in question but basically the reason why I am calling save followed by get is because of the tag + subTag scenario. I didn't want to complicate the processing logic if I was to pass a complicated object to the server for processing. Say if I had to create a tag followed by a subTag the javascript logic would look like this:
...
// skip checking and just add tag and subTag
SaveTag.save({ Name: tag, Desc: "", ParentId: null, GroupId: 12, }, function (data) {
//todo: should wait for data of save operation to comeback
// before moving on to requesting a full object
GetTag.get({ tagId: data.Id, groupId: 12, }, function (data) {
tagObj = data.tag;
});
});
// push newly created tag into tags collection
$scope.tags.push(tagObj);
SaveTag.save({ Name: subTag, Desc: "", ParentId: tagObj.ParentId, GroupId: 12, }, function (data) {
//todo: should wait for data of save operation to comeback
// before moving on to requesting a full object
GetTag.get({ tagId: data.Id, groupId: 12, }, function (data) {
tagObj = data.tag;
});
});
// push newly created sub tag into tags collection
//todo: find parent index
$scope.tags[parent_index]["Elements"].push(tagObj);
...
But in case you wonder, yes, I could return full object from save operation which I probably will in the near future. It's better to reduce number of async calls because they impact overall performance.
But for now I have a few questions:
At the moment I have four different services declared as per Angular documentation. But it would be more efficient if it was a single factory with multiple functions. Can someone point me in the right direction here please.
Is it possible to somehow stop and wait for data.$resolved property to turn true when I call save service so that I can then call get services with returned value? Or perhaps there is an alternative method of doing this?
I am digging into $q of Angular Documentation to see if I can utilise something from here.
Just in case people wonder I have come across a few examples where people utilised resolve property with $routeProvider. Unfortunately my scenario is done in real time during user interactions.
Any help and all advice is greatly appreciated.
References:
AngularJs Docs - $q
Very good explanation and examples - Using and chaining promises in AngularJS
AngularJs Docs - $resource (last example shows use of $promise)
Update:
It seem that my hunch was right. While I haven't got it to work yet I feel the answer lies with $q and chaining promises. Now I just need to get it to work.
I hate to answer my own question but I have found the solution that works.
Update:
OK, after digging about and scratching my head I came up with this code:
// skip checking and just add tag
SaveTag.save({ Name: tag, Desc: "", ParentId: null, GroupId: 12, }).$promise.then(function (data) {
console.log('save tag: ', data);
return data;
}).then(function (data) {
console.log('data: ', data.Id);
GetTag.get({ tagId: data.Id, groupId: 12, }).$promise.then(function (data) {
console.log(' get tag: ', data);
return data;
}).then(function (data) {
console.log('data: ', data.tag);
$scope.tags.push(data.tag);
tagObj = data.tag;
//todo: check for duplicate records
// because repeater will complain and
// it's pointless to have duplicate tags
// on an item to begin with
$scope.myForm.Tags.push(tagObj);
});
});
Notice the $promise after each service call. $promise exposes the raw $http promise that is returned by $resource. This allows me to use .then() to chain additional logic.
So, in the end all I ended up doing is change the way I call my services. That is all.
Related
There is a nice example how Rollup function could be called via MS CRM WebApi here.
But it covers general access to CRM WebApi. Although in most recent versions new JS namespace Xrm.WebApi was introduced. Which provides more straightforward way to access that endpoint.
Method Xrm.WebApi.execute should be able to execute Rollup request, as it is able to execute WhoAmI. But I'm struggling to figure out correct values of parameters to make this execution happen.
Here is my code:
var RollupRequest = function(entityType, id, query) {
this.Target = { entityType: entityType, id: id };
this.RollupType = "Related";
this.Query = {
Query: query
};
};
RollupRequest.prototype.getMetadata = function() {
return {
boundParameter: null,
parameterTypes: {
Target: {
typeName: "Microsoft.Xrm.Sdk.EntityReference",
structuralProperty: 5
},
RollupType: {
typeName: "Microsoft.Dynamics.CRM.RollupType",
structuralProperty: 3
},
Query: {
typeName: "Microsoft.Xrm.Sdk.Query.FetchExpression",
structuralProperty: 5
}
},
operationType: 1, // This is a function. Use '0' for actions and '2' for CRUD
operationName: "Rollup"
};
};
var request = new RollupRequest(
"contact",
"0473FD41-C744-E911-A822-000D3A2AA2C5",
"<fetch><entity name='activitypointer'></entity></fetch>"
);
Xrm.WebApi.execute(request).then(
function(data) {
console.log("Success: ", data);
},
function(error) {
console.log("Failure: ", error);
}
);
The code generates following URL:
/api/data/v9.0/Rollup(Target=#Target,RollupType=#RollupType,Query=#Query)?#Target={"#odata.id":"contacts(0473FD41-C744-E911-A822-000D3A2AA2C5)"}&#RollupType=&#Query={"Query":"<fetch><entity name='activitypointer'></entity></fetch>"}
and the error: "Expression expected at position 0 in ''."
Which, seems to be, indicates that RollupType was not set correctly, because indeed in URL RollupType is missing.
I assume there are more than one potential error, because I'm using FetchXML as query expression. But meanwhile is it possible indicate what should be changed to generate proper URL at least for RollupType property?
newbie here.
I am trying to understand how I need to structure asynchronous calls within my controller to fit my specific use case:
Consider the following code snippet from an Angular Module in "service.js" within my project:
function getSearchObjects(projectName, title) {
var payload = JSON.stringify({
"title": title
});
var request = $http({
method: 'post',
url: URL + '/search/' + projectName,
data: payload
});
return request.then(handleSuccess, handleError);
};
function runQuery(projectName, fromDate, toDate, sort, direction, columns) {
var from = Date.parse(fromDate);
var to = Date.parse(toDate);
var payload = JSON.stringify({
"fromDate": from,
"toDate": to,
"sort": sort,
"direction": direction,
"columns": columns
});
console.log(payload);
var request = $http({
method: 'post',
url: URL + '/query/' + projectName,
data: payload
});
return request.then(handleSuccess, handleError);
}
function handleSuccess(response) {
return response.data;
};
function handleError(response) {
if (!angular.isObject( response.data ) || !response.data.error) {
return( $q.reject( "An unknown error occurred." ) );
}
return $q.reject( response.data.error );
};
});
Within my controller, I am trying to troubleshoot the following function:
$scope.submit = function() {
var objectProperties = exportsStorageService.getSearchObjects($scope.selected.project.name, $scope.selected.search)
.then(function(result) {
exportsStorageService.runQuery($scope.selected.project.name, $scope.selected.start_date, $scope.selected.end_date, objectProperties.sort, objectProperties.direction, objectProperties.columns)
},
function(error) {
console.log(error);
});
};
getSearchObjects matches a title ($scope.selected.search) selected in my UI and grabs the following more detailed object from an API call:
{ title: 'Duplication Example',
sort: '#_traac-timestamp',
direction: 'desc',
columns: [ '#_traac-remote_ip', 'c-platform-m-distinct-id_s', '_type' ] }
I am trying to grab the properties returned from getSearchObjects and pass them along with a few user selected values from my UI to runQuery, which then returns data from a database to the user, but when I check the values passed to runQuery using the above logic in my controller, I get the following values. All of the objectProperties values I am attempting to pass to runQuery are undefined:
project_name: "Example Project"
start_date: 1499770800000
end_date: 1499943600000
sort: undefined
direction: undefined
columns: undefined
I have been trying to debug this, but I am too new to using Angular and asynchronous calls to really understand what I am doing wrong. My best guess currently is that I am calling runQuery before the values retrieved from getSearchObjects are attached to objectProperties. Either that or I am incorrectly referencing the properties within the objectProperties variable.
Could someone help me troubleshoot this issue, and better understand what I am doing wrong?
Thank you in advance for your help!
When you do this:
var objectProperties = some async function...
You are assigning the promise of the async function to the variable, not the result of it.
The result is coming in the .then, like you declared:
.then(function(result) { ... }
So, instead of objectProperties.sort, objectProperties.direction, objectProperties.columns, try using result.sort, result.direction, result.columns :)
If you are new to Promises, take a look at this simple, but great tutorial.
EDIT
Based on your comment, you are receiving, inside the response.data, the following object:
{"objectMatch": {
"title": "doc-event",
"sort": "#_traac-timestamp",
"direction": "desc",
"columns": [
"m-doc-name_s",
"m-user_s",
"m-full-action-type_s",
"m-event-action-descriptor_s"
]}
}
So you have: response > data > objectMatch > properties you want.
The response.data you are extracting on your handleSuccess function:
function handleSuccess(response) {
return response.data;
};
So here, your result is response.data, containing the property objectMatch.
$scope.submit = function() {
var objectProperties = exportsStorageService.getSearchObjects($scope.selected.project.name, $scope.selected.search)
.then(function(result) {
...
},
...
If all of that is correct, you should be able to access the values you want using result.objectMatch.<sort, direction or columns>, like:
exportsStorageService.runQuery($scope.selected.project.name, $scope.selected.start_date, $scope.selected.end_date,
result.objectMatch.sort, result.objectMatch.direction, result.objectMatch.columns)
Trying to return a list of articles specific to a sectionId in angular controller. I can get back the entire list of articles using articles.query but the passed sectionId query param gets completely ignored. I tried a to abstract it to an articles service but I'm not sure how to build services correctly yet so it threw more errors. Any help / examples of how I might achieve this, preferable as a service, would be great. I am using mean.js. Thanks in advance!
Sections controller
angular.module('sections').controller('SectionsController', ['$scope', '$stateParams', '$location', 'Authentication', 'Sections', 'SectionArticlesList', 'Articles',
function($scope, $stateParams, $location, Authentication, Sections, SectionArticlesList, Articles) {
..........
// Find existing Section
$scope.findOne = function() {
$scope.section = Sections.get({
sectionId: $stateParams.sectionId // sections return fine
});
// problem starts here
$scope.articles = Articles.query({
section:$stateParams.sectionId // this param is ignored
});
$scope.articles.$promise.then(function(data) {
// all articles in collection returned instead of section specific
console.log('Articles: '+ JSON.stringify(data));
$scope.articles = data;
});
Articles Model
var ArticleSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true,
required: 'Title cannot be blank'
},
content: {
type: String,
default: '',
trim: true
},
section: {
type: Schema.ObjectId,
ref: 'Section'
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
UPDATE:
Following up on the advice of Kevin B and Brad Barber below, I tried adding a factory and node server route, passing the $stateParams.sectionId to the specific factory instance. I created a new route in the articles.routes.server to make sure not to have a conflict with the standard ‘/articles/:articleId’ route. Unfortunately, anything I try still either throws errors or everything in the articles collection which is the opposite of what I want to do.
section.client.controller
// Find existing Section
$scope.findOne = function() {
$scope.section = Sections.get({
sectionId: $stateParams.sectionId
});
// fails no matter what is passed to query
$scope.articles = SectionArticlesList.query($stateParams.sectionId);
//$scope.SectionArticlesList.query($stateParams.sectionId);
//$scope.articles = SectionArticlesList.query({sectionId});
$scope.articles.$promise.then(function(data) {
// still only ever returns full list of articles
console.log('Length of articles: '+ JSON.stringify(data.length));
$scope.articles = data;
});
….
articles.client.services
angular.module('articles').factory('SectionArticlesList', ['$resource',
function($resource) {
return $resource('articles/:articleSectionId', {
articleSectionId: '#_id'
}, {
update: {
method: 'PUT'
}
});
}
]);
articles.server.routes
// Custom route
app.route('/articles/:articleSectionId')
.get(articles.listBySectionID);
// Custom binding
app.param('articleSectionId', articles.listBySectionID);
articles.server.controller
The server side controller function never appears to get called because neither of the console.logs show up.
exports.listBySectionID = function(req, res, id) {
console.log('listBySection Called....'); // this is never printed
// have tried passing - id, sectionId and {section:id}
Article.find( {section:id} ).sort('-created').populate('user', 'displayName').exec(function(err, articles) {
if (err) {
console.log('listBySection err triggered...'); // this neither
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(articles);
}
});
};
I think I have tried everything I can think of to pass the sectionId correctly but nothing has worked yet. The latest error in the console is the 404 below.Interestingly the sectionId IS getting through but as if it is looking for one single resource such as a single blog post.
GET /sectionArticles?sectionId=555bfaf29a005c30cbfe6931 404
I don't quite understand how the default mean.js /articles route and it's corresponding list function works but duplicating a similar structure and passing an id as a param to the query to retrieve only specific results doesn't.
Would really like to understand how this should be wired up. If anyone can point out what I am doing wrong I’d appreciate it!
i am wanting to change my service from a hardcoded static array to an array returned from a $http call. i have attempted to do this below however it does not work.
I can confirm the data returned from http is working and returns the correct data( i have taken the link out for the purpose of the question).
i am not getting an error message and therefore cannot give any further information at this point however what i would like to know is if i am doing this in the correct way.
banging my head against a wall for such a simple task at the moment....
hardcorded array:
.factory('Cards', function($http){
var cardTypes = [
{id: 1, USECASE_NAME: "Frank", USECASE_IMAGE: 'img/Frank.png', USECASE_DESC:"This is frank the bank card, He helps people all over the world make millions of transactions each year!", done: true },
{id: 2, USECASE_NAME: "John Lewis", USECASE_IMAGE: 'img/JohnLewis.png', USECASE_DESC:"John Lewis is one of the biggest retailers in the United Kingdom with a very proud reputation", done: true },
{id: 3, USECASE_NAME: "Generali", USECASE_IMAGE: 'img/Generali.png', USECASE_DESC:"Generali is the largest insurance company in Italy and arguably one of the biggest in Europe", done: true },
];
return {
all: function() {
return cardTypes;
}
}
});
$http callback
.factory('Cards', function($http) {
var cardTypes = {};
$http.post("http://url", {
"auth": "cats",
"name": "Adam",
"uuid": "fasfA"
}).
success(function(data, status, headers, config) {
cardTypes = data;
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
return {
all: function() {
return cardTypes;
}
}
});
Remember that .http() is an asynchronous call. So calling .all() directly after initalizing the factory will result in returning an empty object, because the post request is probably still pending when .all() is called.
I would recommend using a service (a factory is possible as well, but imo a service would fit better here) and returning the promise like:
this.getAll = function() {
return $http.post("http://url",{"auth":"cats","name":"Adam","uuid": "fasfA" });
}
Then in your controller you can do this:
Cards.getAll().then(function(c){
$scope.cards = c;
})
I've come across this issue before with nested directives, but I managed to find a workaround there. I have code that looks a bit like,
var token = API.callGeneric({}, {method: 'kds.getTokenExtended2', params: ['demo', 'demo', '', '', '', '', '', false, '', '']}); //kds.
token.$promise
.then(function (result) {
if (!angular.isUndefined(result.error)) { // API error
$scope.msg = {iconClass: 'glyphicon-exclamation-sign', txt: 'Looks like there was a problem.'}
if (!APIErr.handle(result.error)) { // handle also returns continueExec flags
return;
}
}
$scope.msg = {iconClass: 'glyphicon-cloud-download', txt: 'almost there…'};
$scope.token = result.result;
console.log('result', result.result);
}, function (error) { // server error
$scope.msg = {iconClass: 'glyphicon-exclamation-sign', txt: 'issues with server, summoning the gods'}
APIErr.handle(error);
})
.then(function (result) {
$scope.msg = {}; // clear the message
// another api call to get bills
return API.callGeneric({}, {method: 'kds.getKitchenDisplayReceipts', params: [$scope.token, new Date().getTime()]});
}, APIErr.handle)
.then(function (result) {
console.log(result); // can see result.result.openReceipts
var receiptIds = result.result.openReceipts; // undefined?
}, APIErr.handle);
And API is a service that calls the API, obviously.
The problem is the last few lines, where console.log(result) shows result.result.openReceipts, and obviously result is a Resource object.
I'm stumped about what might be going on here. Any clues? How can I avoid this in future?
If you want to nest promises you need to return a promise every time.
Your second then is unnecessary in my opinion and could be done inside the first one as the first one is not returning any promises.
So it could be something like:
Pseudo-code:
API.call('token').then(function(result) {
...
return API.call('displayreceipts');
})
.then(function(result){
var recieptIds = result.result.openReceipts;
})
Let me know if it works.