I'm using angular 1.5 with typescript, I can't access this property from the callback being returned from $http promise.
When I'm trying to access a private method from the callback 'this' is undefined
I have the following ServerAPI service:
export class ServerAPI implements IServerAPI {
static $inject:Array<string> = ['$http', '$q'];
constructor(private $http:ng.IHttpService,
private $q:ng.IQService) {
}
postHandler(partialUrl:string, data?:any, config?:any):ng.IPromise<any> {
let url = this.buildUrl(partialUrl);
var result:ng.IPromise< any > = this.$http.post(url, data, config)
.then((response:any):ng.IPromise<any> => this.handlerResponded(response, data))
.catch((error:any):ng.IPromise<any> => this.handlerError(error, data));
return result;
}
private handlerResponded(response:any, params:any):any {
response.data.requestParams = params;
return response.data;
}
private handlerError(error:any, params:any):any {
error.requestParams = params;
return error;
}
}
Which been consumed by user.service:
export class UserService implements IUserService {
static $inject:Array<string> = ['$q', 'serverAPI'];
constructor(private $q:ng.IQService,
private serverAPI:blocks.serverAPI.ServerAPI) {
var vm = this;
$rootScope.globals = $rootScope.globals || {};
$rootScope.globals.currentUser = JSON.parse($window.localStorage.getItem('currentUser')) || null;
this.getUserPermissions();
}
private getUserPermissions:() => IPromise<any> = () => {
var promise = this.serverAPI.postHandler('MetaDataService/GetUserPermissions',
{userID: this.getUser().profile.UserID})
.then((res) => {
this.updateUser('permissions', res.GetUserPermissionsResult); // NOT WORKING, this is undefined
})
.catch((response:any):ng.IPromise<any> => {
this.updateUser('permissions', res.GetUserPermissionsResult); // NOT WORKING, this is undefined
});
return promise;
};
private updateUser:(property:string, value:any) => void = (property, value) => {
};
}
The issue is this line:
.then((response:any):ng.IPromise<any> => this.handlerResponded(response, data))
While your lexical scope is maintained in order to find the handlerResponded method the scope is not fully preserved in the output.
you can get around this in 2 ways:
inline your handler function rather than have it as a function on your class
you can bind the call to handlerResponded to the instance
example of binding:
.then((response:any):ng.IPromise<any> => this.handlerResponded(response, data).bind(this))
Related
I have a angular component and controller that look like this:
export class MyController{
static $inject = [MyService.serviceId];
public elements: Array<string>;
public errorReceived : boolean;
private elementsService: MyService;
constructor(private $elementsService: MyService) {
this.errorReceived = false;
this.elementsService= $elementsService;
}
public $onInit = () => {
this.elements = this.getElements();
console.log("tiles: " + this.elements);
}
private getElements(): Array<string> {
let result: Array<string> = [];
this.elementsService.getElements().then((response) => {
result = response.data;
console.log(result);
}).catch(() => {
this.errorReceived = true;
});
console.log(result);
return result;
}
}
export class MyComponent implements ng.IComponentOptions {
static componentId = 'myId';
controller = MyController;
controllerAs = 'vm';
templateUrl = $partial => $partial.getPath('site.html');
}
MyService implementation looks like this:
export class MyService {
static serviceId = 'myService';
private http: ng.IHttpService;
constructor(private $http: ng.IHttpService) {
this.http = $http;
}
public getElements(): ng.IPromise<{}> {
return this.http.get('./rest/elements');
}
}
The problem that I face is that the array elements contains an empty array after the call of onInit(). However, later, I see that data was received since the success function in getELements() is called and the elements are written to the console.
elements I used in my template to decide whether a specific element should be shown:
<div>
<elements ng-show="vm.elements.indexOf('A') != -1"></elements>
</div>
The problem now is that vm.elements first contains an empty array, and only later, the array is filled with the actual value. But then this expression in the template has already been evaluated. How can I change that?
Your current implementation doesn't make sense. You need to understand how promises and asynchronous constructs work in this language in order to achieve your goal. Fortunately this isn't too hard.
The problem with your current implementation is that your init method immediately returns an empty array. It doesn't return the result of the service call so the property in your controller is simply bound again to an empty array which is not what you want.
Consider the following instead:
export class MyController {
elements: string[] = [];
$onInit = () => {
this.getElements()
.then(elements => {
this.elements = elements;
});
};
getElements() {
return this.elementsService
.getElements()
.then(response => response.data)
.catch(() => {
this.errorReceived = true;
});
}
}
You can make this more readable by leveraging async/await
export class MyController {
elements: string[] = [];
$onInit = async () => {
this.elements = await this.getElements();
};
async getElements() {
try {
const {data} = await this.elementsService.getElements();
return data;
}
catch {
this.errorReceived = true;
}
}
}
Notice how the above enables the use of standard try/catch syntax. This is one of the many advantages of async/await.
One more thing worth noting is that your data services should unwrap the response, the data property, and return that so that your controller is not concerned with the semantics of the HTTP service.
i'll try to inject a service in my controller with TypeScript and angular JS. But not working. What's is the problem in my code ? It's my service the problem ? All function in my service is not accessible, all are undefined when i'll try in debug. There is my code.
Controller
module Controller {
import Service = Services;
export class UserCtrl implements Interface.IUserCtrl {
private user: Model.User;
private users: Model.User[];
private userService: Service.UserService;
static $inject = ['$scope','UserService'];
constructor(userService: Service.UserService) {
this.userService = userService;
}
public getUsers = () => {
this.users = this.userService.findAll();
return this.users;
}
public getUser = (name: string) => {
return this.userService.find(name);
}
}
app.controller('UserCtrl', Controller.UserCtrl);
}
Service
module Services {
export class UserService implements Interface.IUserService {
private users: Model.User[];
constructor() {
this.users = [new Model.User("Giunta", "Lucas", 26), new Model.User("Rousselet", "CĂ©line", 26)];
}
public create = (user: Model.User) => {
}
public edit = (user: Model.User) => {
}
public remove = (user: Model.User) => {
}
public find = (name: string) => {
for (let u of this.users) {
if (u.name == name) {
return u;
}
}
}
public findAll = () => {
return this.users;
}
}
app.service('UserService', Services.UserService);
}
It's because you request both $scope and the UserService, but the constructor only accepts the later.
To fix it you should have the properties in the constructor in the same order as the ones in $inject.
static $inject = ['$scope','UserService'];
constructor($scope, userService: Service.UserService) {
this.userService = userService;
}
As #toskv mentioned, you need to also inject $scope into your constructor.
The dependencies are in order, so with the way your code is now, the injector thinks that your injected UserService is $scope which is not what you want.
I've got an angular service MyService
There is get method inside it. It get's info from server and sets it to local variable if variable is undefined, overwise return variable
export class MyService{
private userSettings: UserSettings;
private updateProcessing: boolean = false;
private deferred : any;
constructor(
private $http: ng.IHttpService,
private $q: ng.IQService,
private $log: ng.ILogService) {
}
public get(fromServer: boolean = false) {
var self = this;
if (self.updateProcessing) {
return self.deferred.promise;
}
else if (!self.userSettings || fromServer) {
return self.getFromServer();
} else
return self.$q.resolve(self.userSettings);
}
private getFromServer(): ng.IPromise<any> {
var self = this;
self.updateProcessing = true;
self.deferred = self.$q.defer();
var url = self.getSettingsUrl();
self.$http.get(url).then(
(result: any) => {
self.userSettings = result.data;
self.updateProcessing = false;
self.deferred.resolve(result.data);
},
error => {
this.$log.error(error);
self.updateProcessing = false;
self.deferred.reject(error);
}
);
return self.deferred.promise;
}
}
when I pass this service to 3 different controllers, they all get variable value from server.
I'm trying to save promise and if request is already prosessing whait while it resolves and do not create new one.
Right now with code I posted I do in my controllers
this.MyService.get().then(()=>{
});
and never get inside then callback.
If it's important, I use version: "1.5.8" of angular library.
You can share the same promise. Also note that $http already returns promise so using $q to create a new one is an anti-pattern
private getFromServer(): ng.IPromise < any > {
var self = this;
self.updateProcessing = true;
// if promise not stored need to create it
if (!self.storedPromise) {
var url = self.getSettingsUrl();
self.storedPromise = self.$http.get(url).then(
(result: any) => {
self.userSettings = result.data;
self.updateProcessing = false;
return self.userSettings;
},
catch => {
this.$log.error(error);
self.updateProcessing = false;
return self.$q.reject(error);
}
);
}
// return the stored promise
return self.storedPromise;
}
Now the first call to this method will create the promise and subsequent calls will return the same one
I created a service using Typescript:
class ConfigService implements IConfigService {
public admin = {};
public adminBackup = {};
public user = {};
public loaded = false;
constructor(
private $http: ng.IHttpService,
private $q: ng.IQService
) {
}
static $inject = [
'$http',
'$q'
];
put = ():ng.IHttpPromise<> => {
var defer = this.$q.defer();
if (angular.equals(this.admin, this.adminBackup)) {
return defer.resolve();
} else {
this.$http({
data: {
adminJSON: JSON.stringify(this.admin),
userJSON: JSON.stringify(this.user)
},
url: '/api/Config/Put',
method: "PUT"
})
.success(function (data) {
this.adminBackup = angular.copy(this.admin);
this.userBackup = angular.copy(this.user)
return defer.resolve();
});
}
return defer.promise;
};
}
I also created this interface:
interface IConfigService {
put(): ng.IHttpPromise<>;
}
However the code is giving me an error saying:
Error 3 Cannot convert 'void' to 'ng.IHttpPromise<any>'.
Error 4 Cannot convert 'ng.IPromise<{}>' to 'ng.IHttpPromise<any>':
Type 'ng.IPromise<{}>' is missing property 'success' from type 'ng.IHttpPromise<any>'.
Use
ng.IPromise<void>
Also you could let it implicitly type it for you if you don't declare a return type and don't use that interface.
Also there should be no return statement here :
return defer.resolve();
Just :
defer.resolve();
When I ran into this issue I found that I wasn't able to use ng.IPromise<void> as the declarations for ng.IHttpPromise<> demanded that I return an object.
For example:
public saveTechnicianNote = (jobNumber: string, technicianNote: Model.TechnicianNote): ng.IPromise<void> => {
var data = {
jobNumber: jobNumber,
subject: technicianNote.subject,
details: technicianNote.details
};
return this.$http.post(this.urlFactory.saveTechnicianNote, data);
}
threw up an error that Type IHttpPromise<{}> is not assignable to IPromise<void>.
To get around this I made it return ng.IPromise<any>. Not 100% ideal but it was the best solution I could come up with that didn't require me to extend on angular.d.ts
Final Code:
public saveTechnicianNote = (jobNumber: string, technicianNote: Model.TechnicianNote): ng.IPromise<any> => {
var data = {
jobNumber: jobNumber,
subject: technicianNote.subject,
details: technicianNote.details
};
return this.$http.post(this.urlFactory.saveTechnicianNote, data);
}
I want to write a TypeScript class that gets a "prefix" parameter in the constructor, this class also needs access to a LogService inject.
Using plain JavaScript you should do it like this:
angular.module('myModule', []).factory('LogWithPrefixFactory', ['LogService', function(LogService) {
var LogWithPrefixFactory = function(prefix) {
this.prefix = prefix;
}
LogWithPrefixFactory.prototype.log = function(txt) {
// we have access to the injected LogService
LogService.log(this.prefix, txt);
}
return LogWithPrefixFactory;
}]);
So when you inject this factory to a controller, you can initiate it many times like this (No need to inject the LogService):
angular.module('myModule').controller('Ctrl', function(LogWithPrefixFactory) {
var foo = new LogWithPrefixFactory("My PREFIX");
var foo = new LogWithPrefixFactory("My OTHER PREFIX");
}
How would you define this Factory in a TypeScript class?
TypeScript classes can not be defined inside functions...
This class should have access to the LogService, but it can't get it in one of the injects.
The following is one way to achieve this:
class LogWithPrefixFactory {
static LogService;
constructor(prefix) {
this.prefix = prefix;
}
log = function(txt) {
// we have access to the injected LogService
LogService.log(this.prefix, txt);
}
}
angular.module('myModule', []).factory('LogWithPrefixFactory', ['LogService', function(LogService) {
LogWithPrefixFactory.LogService = LogService;
return LogWithPrefixFactory;
}]);
angular.module('myModule').controller('Ctrl', function(LogWithPrefixFactory) {
var foo = new LogWithPrefixFactory("My PREFIX");
var foo = new LogWithPrefixFactory("My OTHER PREFIX");
});
Rational: You effectively want a static property in a the LogWithPrefixFactory (using a closure in JS) , and you want it to come from Angular.
There are at least 2 options.
First option, have LogWithPrefixFactory provide a method getInstance that returns the prefixed logger.
module services {
class LogService {
$window: any;
constructor($window: any) {
this.$window = $window;
}
log(prefix: string, txt: string) {
this.$window.alert(prefix + ' :: ' + txt);
}
}
angular.module('services').service('LogService', ['$window', LogService]);
export interface ILog {
log: (txt) => void;
}
export class LogWithPrefixFactory {
logService: LogService;
constructor(logService: LogService) {
this.logService = logService;
}
getInstance(prefix: string): ILog {
return {
log: (txt: string) => this.logService.log(prefix, txt);
}
}
}
angular.module('services').service('LogWithPrefixFactory', ['LogService', services.LogWithPrefixFactory]);
}
Which can be used in the controller like:
this.log1 = logWithPrefixFactory.getInstance("prefix1");
this.log2 = logWithPrefixFactory.getInstance("prefix2");
Complete plunker here.
Second option (similar to another answer), give Angular another function to be used as a constructor, which handles manually the LogService constructor injection (personally, I don't like static).
angular.module('services').service('LogWithPrefixFactory', ['LogService', function(logService) {
return function LogWithPrefixFactory(prefix) {
return new LogWithPrefix(prefix, logService);
};
}]);
Which can be used in the controller like:
this.log1 = new LogWithPrefixFactory("prefix1");
this.log2 = new LogWithPrefixFactory("prefix2");
or even:
this.log1 = LogWithPrefixFactory("prefix1");
this.log2 = LogWithPrefixFactory("prefix2");
LogWithPrefixFactory is injected in the controller but it's not the TypeScript class constructor, it's the intermediate function which returns the actual instance of the class, after it has been "manually" injected with LogService.
Complete plunker here.
Note: These plunkers synchronously compile typescript on the browser. I have tested it only on Chrome. No guarantees that they'll work. Finally, I manually added a small part of angular.d.ts. Full file was very big and my proxy does not allow large POSTs.
I have achieved like below
module Dashboard {
export class LayoutServiceFactory {
static $inject = ["$q", "$http"];
private q: ng.IQService;
private http: ng.IHttpService;
constructor(private $q: ng.IQService, private $http: ng.IHttpService) {
this.q = $q;
this.http = $http;
}
getDataFromServer(serviceUrl) {
var deferred = this.q.defer();
this.http.get(serviceUrl, null)
.then(response => {
deferred.resolve((response) as any);
});
return deferred.promise;
}
static factory() {
var instance = ($q: ng.IQService, $http: ng.IHttpService) =>
new LayoutServiceFactory($q, $http);
return instance;
}
}
appModule.factory("LayoutService", LayoutServiceFactory.factory());
}
This worked for me.
namespace Services
{
export class MyService
{
constructor( protected $someService :any )
{
return this;
}
}
}
angular.module( 'myModule', [] ).factory( Services );
this is how i do it
namespace app {
let app =angular.module('foo',[]);
app.factory(factories);//for registering whatever is there in factories namespace
}
namespace app.factories {
export class fooFactory {
static $inject = ['fooHelperService']
constructor(fooHelperService: services.fooHelperService) {
return {
fooFunc: () => {
return 'hellow world'
}
}
}
}
}
You can create a type that allows you to define what the constructor of the factory looks like:
// Defining the factory
// THIS IS THE IMPORTANT PART!!
export type SelectorFactory = new (config: any) => Selector;
export class Selector {
constructor(protected config: any, protected $http: ng.IHttpService) {
// do some stuff
}
}
angular.module('app')
.factory('Selector', ($http: ng.IHttpService) => {
// This is what the factory looks like to the end user
return (config: any) => {
return new Selector(config, $http);
};
});
// Using the factory
export class SampleCtrl {
constructor(public SelectorFactory: SelectorFactory) {
let config = { op: 1 };
let selector: Selector = new SelectorFactory(config);
}
}