I am creating a templateparser that can parse multiple templates and return the generated html content. For binding the html and view information I am using the Angular $compile service. The problem I am encountering is that the promise .then() is called before the promise is resolved (and thus results in undefined).
AngularJS version: 1.6.3
The parser function
/**
* Using the $compile function, this function generates a full HTML page based on the given process and template
* It does this by binding the given process to the template $scope and uses $compile to generate a HTML page
* #param {Afhandeling} process - The process that can bind to the template
* #param {string} templatePath - The location of the template that should be used
* #param {boolean} [useCtrlCall=true] - Whether or not the process should be a sub part of a $ctrl object. If the template is used
* for more then only an email template this could be the case (EXAMPLE: $ctrl.<process name>.timestamp)
* #return {IPromise<string>} A full HTML page
*/
public createEmail(process: Afhandeling, templatePath: string, useCtrlCall = true): ng.IPromise<string> {
let processScope = {};
if (useCtrlCall) { //Create scope object | Most templates are called with $ctrl.<process name>
const controller = "$ctrl";
processScope[controller] = {};
processScope[controller][process.__className.toLowerCase()] = process;
} else {
processScope = process;
}
return this.$http.get(templatePath)
.then((response) => {
let template = response.data;
let scope = this.$rootScope.$new();
angular.extend(scope, processScope);
let generatedTemplate = this.$compile(jQuery(template))(scope);
let waitForRenderCompletion = () => {
if (scope.$$phase || this.$http.pendingRequests.length) {
console.warn("Time for a timeout.");
this.$timeout(waitForRenderCompletion);
} else {
console.warn("Lets return the template.");
return generatedTemplate[0].innerHTML;
}
};
waitForRenderCompletion();
})
.catch((exception) => {
console.error(exception);
this.logger.error(
TemplateParser.getOnderdeel(process),
"Email template creation",
(<Error>exception).message
);
return null;
});
}
The function call
this.templateParser.createEmail(
this.model,
"<template url>"
).then((template: string) => {
console.warn(template); //Results in 'undefined'
});
The reason I am watchting $$phase for changes is because of $compile not giving any feedback on when it is done compiling. The template can consist of an undefinite number of templates bound together by ng-include's. ng-includes are also async so I cannot think of any other way to check when the $compile is done (My question about a better solution then this).
What I am thinking
When I look at the console output I get the following:
Time for a timeout.
undefined
(2) Time for a timeout.
Lets return the template.
So it seems like the promise is automatically resolved when the first $timeout resolves. Yet this doesn't make any sense, since I am not returning anything.
Any help is appreciated.
Answer
Thanks #charlietfl for the hint. The working code is below. I'm now returning the function, so that I have a return value in my promise. I am also returning the $timeout, so that the function can be called recursively.
The code:
let waitForRenderCompletion = () => {
if (scope.$$phase || this.$http.pendingRequests.length) {
console.warn("Time for a timeout.");
return this.$timeout(waitForRenderCompletion);
});
} else {
console.warn("Lets return the template.");
return generatedTemplate[0].innerHTML;
}
};
return waitForRenderCompletion();
I'm having a problem getting at values in my service from the controller. My service looks like this:
angular.module('someApp').factory('someSvc', SomeSvc);
function SomeSvc($http) {
var colors = [];
function loadColors() {
return $http.get('SomeApi/GetColors')
.then(function (result) {
//colors = result.data.colors;//<-this doesn't work
//angular.copy(result.data.colors, colors);//<-this works
});
}
return {
loadColors: loadColors,
colors: colors
};
}
Then my controller might make a call like this:
someSvc.loadColors().then(function(){vm.colors = someSvc.colors;});
So, when I debug, if I set a breakpoint in the controller where the assignment to vm.colors is made, the colors property exposed on the someService object has just an empty array or any array with the expected values depending on which of the two commented-out lines I use in the service.
If I set a breakpoint in the service where the assignment to colors is made, the variable colors always has the expected values (e.g., let's say ["red", "yellow", "green"] is what comes back from the http call). So I can watch the controller trigger the http call, watch the value come back and get assigned to colors in the service, but then the controller just sees an empty array unless I do that angular.copy call.
Also, interestingly, if I change the service's return statement to look like this:
return {
loadColors: loadColors,
colors: function() {return colors;}
};
and then in the controller say vm.colors = someSvc.colors(); then that works just fine as well.
Why is this? Why isn't that array getting passed through?
UPDATE:
I've found that instead of the angular.copy() line, I can alternatively do this, and everything works as expected:
for (var i = 0; i < result.data.colors.length; i++) {
colors[i] = result.data.colors[i];
}
It seems to be that ASSIGNING the object is a problem, while modifying it is ok? Why is that?
This might work for ya. Guessing it's just a pointer issue maybe?
angular.module('someApp')
.factory('someSvc', function($http)
{
return {
colors: [],
loadColors: function()
{
var self = this;
return $http.get('SomeApi/GetColors').then(function (result)
{
self.colors = result.data.colors;
});
}
};
});
At the time you're calling return in your Factory, someSvc.colors is just the empty array - and the value is returned. Since Angular providers in general attempt to run only once, in future it doesn't actually check someSvc.colors again - just returns the initial value.
Wrapping it in a function means it runs the function every time, so it fetches the updated value.
I'm trying to return a value from a WebDriver promise within a Protractor solution using TypeScript, but I'm getting undefined as response.
get nameInput(): string {
var value: string;
this.nameElement.getAttribute('value').then(v => value = v);
return value;
}
In the above case it seems like the function is not waiting for the promise to return, therefore I tried moving away from the getter style and declared the return type as WebDriver's promise:
getNameInput(): webdriver.promise.Promise<string> {
var nameElement = element(by.id('name'));
return nameElement.getText().then(v => { return v });
}
But I'm getting Function as the return instead of the value of v. Seems like the promise is not being unwrapped by Jasmine's expect, as it happens when running it in JS.
I know I can run the promise directly within the expect, but ideally I would create all the function's logic outside of any expectations, so that I can then feed the expectation the function call with any (if any) parameters only - instead of polluting the test case with promise logic.
Any ideas?
You don't need to resolve the promises, just return them:
getNameInput(): webdriver.promise.Promise<string> {
var nameElement = element(by.id('name'));
return nameElement.getText();
}
Then you need to have a real value returned from the getNameInput() - resolve it in your test:
getNameInput().then(v => { console.log(v) });
Note that, you also let expect() resolve it implicitly:
expect(getNameInput()).toEqual("Expected Value");
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.
Im working on an extjs application. We're have a page that is for looking at a particular instance of an object and viewing and editing it's fields.
We're using refs to get hold of bits of view in the controller.
This was working fine, but I've been sharding the controller into smaller pieces to make it more managable and realised that we are relying on a race condition in our code.
The logic is as follows:
Initialise the controller
parse the url to extract the id of the object
put in a call to load the model with the given view.
in the load callback call the controller load method...
The controller load method creates some stores which fire off other requests for bits of information using this id. It then uses some of the refs to get hold of the view and then reconfigures them to use the stores when they load.
If you try and call the controller load method immediately (not in the callback) then it will fail - the ref methods return undefined.
Presumably this is because the view doesnt exist... However we aren't checking for that - we're just relying on the view being loaded by the time the server responds which seems like a recipe for disaster.
So how can we avoid this and be sure that a view is loaded before trying to use it.
I haven't tried rewriting the logic here yet but it looks like the afterrender event probably does what I want.
It seems like waiting for both the return of the store load and afterrender events should produce the correct result.
A nice little abstraction here might be something like this:
yourNamespace.createWaitRunner = function (completionCallback) {
var callback = completionCallback;
var completionRecord = [];
var elements = 0;
function maybeFinish() {
var done = completionRecord.every(function (element) {
return element === true
});
if (done)
completionCallback();
}
return {
getNotifier: function (func) {
func = func || function (){};
var index = elements++;
completionRecord[index] = false;
return function () {
func(arguments);
completionRecord[index] = true;
maybeFinish();
}
}
}
};
You'd use it like this:
//during init
//pass in the function to call when others are done
this.waiter = yourNamespace.createWaitRunner(controller.load);
//in controller
this.control({
'SomeView': {
afterrender: this.waiter.getNotifier
}
});
//when loading record(s)
Ext.ModelManager.getModel('SomeModel').load(id, {
success: this.waiter.getNotifier(function (record, request) {
//do some extra stuff if needs be
me.setRecord(record);
})
});
I haven't actually tried this out yet so it might not be 100% but I think the idea is sound