Why do i have to re-declare properties in typescript angular controller functions - angularjs

export interface IFooModel {
foo:string;
fooFunction(fooProp:string):void;
}
export class FooCtrl implements IFooModel {
constructor(public foo:string){
}
fooFunction(fooProp:string):void{
}
}
The code above is fairly standard. My question is , when i want to access foo:string in the function i have to do this
fooFunction(fooProp:string):void{
var fooAgain = this.foo;
// Pretend i set it up properly for $mdDialog to work
this.$mdDialog.show(options).then(function(answer: boolean) {
if (answer) {
// fooAgain works
// this.foo does not work
}
}
Why do i have to set this.foo to a variable in order to access it inside another function , instead of just writing this.foo ? In some functions i end up for about 4 variables declarations that are already declared in the constructor. Is there maybe a better way to this? I get the feels that there is too much repeat code in the controller.

Yes this is a problem in Javascript, but thankfully in TypeScript this problem is no more thanks to fat arrows! Yay!
Fat arrows are like anonymous functions but handle the this variable for you.
Let me show you:
fooFunction(fooProp:string):void {
// Pretend i set it up properly for $mdDialog to work
this.$mdDialog.show(options).then((answer: boolean) => {
if (answer) {
this.foo = "";
}
});
}
Using fat arrows this will now compile to this in JS:
FooCtrl.prototype.fooFunction = function (fooProp) {
var _this = this;
// Pretend i set it up properly for $mdDialog to work
this.$mdDialog.show(options).then(function (answer) {
if (answer) {
_this.foo = "";
}
});
};
So Typescript automatically creates a _this variable for you, so that you no longer have the problem. Pretty neat if you ask me.
Here's the documentation for Arrow functions:
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions

The only reason to place the value in a local variable prior to calling a function is because you expect the class to go out of scope (i.e. something will happen to change the scope, such as a callback or asynchronous operation).
By putting the value in a local variable it becomes part of the closure for the function and the value is stored alongside the function itself.
This is one of the trickiest aspects of JavaScript - scope is kinda funky.
There are other options to solve this problem, including fat arrows...
() => {
// this.foo is now preserved
}
Or call / apply which allow you to set the scope.

Related

Angular and Typescript: proper way to reference 'this'

I am setting typescript in an angular project. In order to declare a controller I use the following syntax:
module app {
class MyController {
public myvar: boolean;
constructor() {
this.myvar= false;
}
}
angular.module("app").controller("MainController", [MainController]);
}
Please note that I don't inject the scope, I only use inner properties / methods of the controller.
But I don't like to access to properties with 'this', usually I should declare:
var vm = this.
vm.myvar = ...
However this is annoying as I have many methods; I should declare this in any ones, this is repetitive.
Is there a best practice and/or a pattern, in order to declare the 'vm' only once?
But I don't like to access to properties with 'this', usually I should declare var vm = this ... Is there a best practice and/or a pattern, in order to declare the 'vm' only once?
It's a good time to drop that practice. In TypeScript it's easy to just use this and not assign this to a variable—it's already defined for you so it's nice to use it.
The key when doing this is to use arrow functions to make sure you always use the class instance's this and not the this bound to a regular function expression.
class MyController {
myVar = false;
someOtherMethod() {
// good
functionWithCallback(() => {
// this will be the class instance
console.log(this.myVar);
});
// bad
functionWithCallback(function() {
// this will not be the class instance
console.log(this.myVar);
});
// good
functionWithCallback(() => this.myOtherMethod());
// bad, `this` in myOtherMethod is not the class instance
functionWithCallback(this.myOtherMethod);
}
myOtherMethod() {
console.log(this.myVar);
}
}
function functionWithCallback(callback: Function) {
callback();
}

How to properly save self reference with ES6 classes?

Honestly, I'm not sure of what is the cause for the behavior: systemjs, babel or my own fault. I'm using class for custom control controller and saving class reference in self variable. Apparently that gets overriden by any subsequent controller instances.
I created a simple repository to demonstrate:
clone, install, run live-server or your preferred server. You will see 2 buttons, each is a custom control. Clicking on a button only affects one control.
https://github.com/alexkolt/selfIsThis
How can I get this working with ES6 class?
I should have posted the code, sorry.
The reason you'd want to save reference to self is for example in callbacks calling this might result in a different reference.
I was trying to do this:
var self;
class Test {
constructor(dependency) {
self = this;
self.dependency = dependency;
}
method() {
self.dependency().then(value => self.property = value);
}
}
Like it was mentioned before the self becomes shared when declared outside of the module. I didn't realize that would happen as files would be wrapped in a closure. Joe Clay answer is correct, but to do what I was trying to do self needs to be declared in every method that needs it.
class Test {
constructor(dependency) {
this.dependency = dependency;
}
method() {
var self = this;
this.dependency().then(value => self.property = value);
}
}
You're not really using ES6 classes right. You don't need to save a reference to this - just access it directly in class methods. The way you have it at the minute, all your instances of CustomControlController are sharing a single self variable.
class CustomControlController {
constructor() {
this.value = 0;
}
click() {
var newValue = this.value * 2;
this.value = newValue;
}
}
export default CustomControlController;

Code Highlighting in PHPStorm/WebStorm for Angularjs

In my controller, I have a function defined as:
var ProductListingHeaderController = function (FilterService, CategoryService) {
this.isCategorySet = function () {
return (FilterService.categoryID);
};
this.categoryName = function () {
return (CategoryService.categoryName());
};
};
The IDE (via code highlighting) reports categoryName() as being used and isCategorySet() as unused.
This is kind of understandable, since:
categoryName() is used inside {{ }} in the html file:
<h2>{{productListingHeader.categoryName()}}</h2>
and isCategorySet() is used in an ng-if string:
ng-if="productListingHeader.isCategorySet()"
Given that this is such a common usage, I suspect I may be missing a setting in Storm as to how to set things up so that this type of usage (inside a string) by an Angular directive gets picked up as "used".
Thanks in advance for any feedback.
That's normal behavior for PHP/WebStorm.
Template -> JavaScript is really just an ambiguous connection. There is no support for jsDoc type inferring in the AngularJS templates. So PHP/WebStorm will match a template function call to JavaScript if only that function name is unique.
PHP/WebStorm have issues inferring closure methods as object functions. I've had better success using prototype declaration for AngularJS controllers.
var ProductListingHeaderController = function (FilterService, CategoryService) {
this.filterService = FilterService;
this.categoryService = CategoryService;
}
ProductListingHeaderController.prototype.isCategorySet = function () {
return (this.filterService.categoryID);
};
ProductListingHeaderController.prototype.categoryName = function () {
return (this.categoryService.categoryName());
};
Compare the above code with your code, and look at the structure explore in WebStorm. When you use prototypes for controllers they appear in the explorer properly.

this point set to null using afterSelectionChange in ngGrid

I'm writing an application using angular and typescript.
I'm using ng-grid and I have to handle the afterSelectionChange event.
I tried to set the event handler in two ways
this.$scope.settoriGridOptions.afterSelectionChange = this.afterSelectionChange;
where this.afterSelectionChange is a method of the controller class,
and
this.$scope.settoriGridOptions.afterSelectionChange = (... ) => {};
including the code inside, but in both cases the this pointer is incorrect and I cannot access to the services of the controller.
how can I fix this?
after a more tests and reading a few articles I see that the problem is the implicit passing of the this pointer as parameter in the function call.
if I write
$scope.filtroSoluzione = this.filtroSoluzione;
when called the this pointer is set to null, but if I write
$scope.filtroSoluzione = () => { return this.filtroSoluzione() };
or
$scope.filtroSoluzione = () => { .. inline code ... };
the this pointer I set correctly.
How can I have a more consistent behavior? I don't like to write always the code inside because this makes the class harder to read and navigate
thanks,
Luca
Thanks for the extra information in your edits, I now see the problem.
class foo {
public afterSelectionChange = () => {
console.log(this);
}
}
When you declare your function like I did above, your this is the instance instead of what you are seeing know because it captures the this variable. It comes with a cost though, because now it creates a new afterSelectionChange function for every instance of your class. In this case I think it is still what you want though.
var foo = (function () {
function foo() {
var _this = this;
this.afterSelectionChange = function () {
console.log(_this);
};
}
foo.prototype.baz = function () {
console.log(this);
};
return foo;
})();
In the above code-gen you can see the difference when declaring the function with name = () => {} and the normal way.
Another solutions might be this:
this.$scope.settoriGridOptions.afterSelectionChange = this.afterSelectionChange.bind(this);
But I don't find that really nice either... (this should work with the normal public afterSelectionChange() {} declaration you are used to.

Angularjs promise not binding to template in 1.2

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>
...

Resources