Setting context of "this" from another typescript class, using AngularJS dependency injection - angularjs

I'm using a TypeScript class to define a controller in AngularJS:
class TrialsCtrl {
constructor(private $scope: ITrialsScope, private ResourceServices: ResourceServices) {
this.loadTrials();
}
loadTrials() {
console.log("TrialsCtrl:", this);
this.Trial.query().then((result) => {
this.$scope.Trials = result;
});
}
remove(Trial: IRestTrial) {
this.ResourceServices.remove(Trial, this.loadTrials);
}
}
angular.module("app").controller("TrialsCtrl", TrialsCtrl);
I'm refactoring common controller methods into a service.
class ResourceServices {
public remove(resource, reload) {
if (confirm("Are you sure you want to delete this?")) {
resource.remove().then(() => {
reload();
});
}
}
}
angular.module("app").service("ResourceServices", ResourceServices);
The console log shows that this is referencing the window context when I want it to be TrialsCtrl. My problem is that the reload() method needs to run in the context of TrialsCtrl, so that it can access this.Trial and this.$scope. How can I tell the reload() method to set this as the TrialsCtrl? Or is there some other workaround I should be using for this kind of thing?

Have you tried:
this.ResourceServices.remove(Trial, this.loadTrials.bind(this));
or
this.ResourceServices.remove(Trial, () => this.loadTrials());

For methods that are supposed to be passed as callbacks (as with this.loadTrials) it is preferable to define them as arrows,
loadTrials = () => { ... }
So they keep the context whether Function.prototype.bind is used or not.
Alternatively, a decorator may be used on the method (like core-decorators #autobind) to bind a method while still defining it on class prototype:
#autobind
loadTrials() { ... }

Related

Angular Typescript - using a custom filter inside a controller

I have a custom filter in my angular application (typescript) as below:
namespace InterACT {
export namespace Filters {
export class ContractAppliedTo {
public static onFilterApplied(contract: Interfaces.IReimbursementContract) {
if (contract.services && contract.services.length > 0) {
return _.map(contract.services, 'regNumber').join(', ');
} else if (contract.operator) {
return contract.operator.name;
}
return contract.scheme.abbreviation;
}
}
}
}
angular
.module('app.settlement')
.filter('contractAppliedTo', () => { return InterACT.Filters.ContractAppliedTo.onFilterApplied; });
I am able to use this filter just fine in my markup like other built in filters:
{{rule | contractAppliedTo}}
I now need to use this filter in a controller, and am struggling how to reference it for usage.
I already use angular date filters in some of my controllers, and in the constructor of these, I am injecting in ng.IFilterService and assigning to a private variable for usage later on.
namespace InterACT {
export namespace Controllers {
export class MyController {
public static $inject = ['$filter'];
constructor(
private $filter: ng.IFilterService
) {
}
public someFunction = () => {
let dateFilter = this.$filter('date'),
foo;
foo = dateFilter('some-date-string', 'd-M-Y');
}
}
}
}
When it comes to using my ContractAppliedTo filter, I can't get the controller to recognise it for me to use, or I guess what I mean is the typescript compiler wont pick it up.
I wondered if I could use the filter service to grab an instance of my new filter, e.g:
let contractAppliedTo = this.$filter('contractAppliedTo')
But when I try to use it, I get a typescript error saying
[ts] Cannot invoke an expression whose type lacks a call signature. Type '{}' has no compatible call signatures.
I think I am missing an interface in order to be able to use this filter in my controller.
Can someone advise further please?
Thanks
This issue is that the FilterService cannot find the call signature for your custom filter. I was able to get around the issue by importing the custom filter and using type assertion against the usage of the $filter service.
const contractAppliedTo: contractAppliedTo =
(<contractAppliedTo>this.$filter('contractAppliedTo'));

Angular2 dont fire changeDetection after click

Angular2 doesn't trigger the ChangeDetection after a click event. The code snippets below are to get the data from one component to another.
onClickEvent
(click)="$event.preventDefault(); setApartmentObject(FlatObject)";
ApartmentOverviewComponent
constructor(private _apart:ApartmentService) {}
setApartmentObject(flat:ApartmentObject) {
this._apart.setApartmentDetails(flat);
}
ApartmentService
Injectable()
export class ApartmentService {
apartmentDetails:ApartmentObject
getApartmentDetails():Observable<ApartmentObject> {
return Observable.create((observer) => {
observer.next(this.apartmentDetails);
observer.complete();
});
}
setApartmentDetails(value:ApartmentObject) {
this.apartmentDetails = value;
}
}
ApartmentDetailComponent
constructor(private _apart:ApartmentService)
get apartmentDetails() {
this._apart.getApartmentDetails().subscribe(data => {
this._apartmentDetails = data;
});
return this._apartmentDetails;
}
In the HTML file
<p><strong>{{apartmentDetails.name || 'Musterwohnung'}}</strong></p>
I also tried to fix this problem with an eventemitter, but without success. Only the following dirty fix works:
constructor(private _ref:ChangeDetectorRef) {
this._ref.detach();
setInterval(() => {
this._ref.detectChanges();
}, 300);
}
There are some issues with your code that actually prevent the value from being read.
First of all—in your service—when you set the value, you just do it on the service's instance, instead of feeding it to the observable object. The observable just can't know that value has changed, so it won't emit the change (next) event. This is why the ApartmentOverviewComponent. setApartmentObject() does nothing. To actually feed the observable with data, you need to use a Subject.
In the ApartmentDetailComponent, in this simple scenario (where data is always synchronously provided), you could get the value in the way you try it. But, as mentioned before, the data won't ever change. It's also needles to store the data on the component's instance's _apartmentDetails field. You could use the observable in your template.
The working implementation is like that:
#Injectable()
class ApartmentService {
// BehaviorSubject is a type of an Observable that can be manually fed with data
// and returns it's last value to any subscriber.
apartmentDetails = new BehaviorSubject<ApartmentObject>({name: 'Musterwohnung'});
// Instead of using a property of the service, just inform the
// subject about knew data and let it spread the change for you.
setApartmentDetails(value: ApartmentObject) {
this.apartmentDetails.next(value);
}
}
#Component({
selector: 'overview-cmp',
// Side note: you don't need to .preventDefault() here.
template: `<a (click)="setApartmentObject({name: 'Shiny Aparament'})">click</a>`
})
class ApartmentOverviewComponent {
constructor(private apartService: ApartmentService) {}
// Works same as before.
setApartmentObject(flat: ApartmentObject) {
this.apartService.setApartmentDetails(flat);
}
}
#Component({
selector: 'details-cmp',
// Use the 'async' pipe to access the data stored in an Observable
// object. Also, to secure the code, use '?' to safely access the property.
template: `<p><strong>{{(details | async)?.name}}</strong></p>`
})
class Apartament {
// This is the observable with data.
details: Observable<ApartmentObject>;
constructor(private apartService: ApartmentService) {}
// When component initialises, assign the observable data from the service
ngOnInit() {
this.details = this.apartService.apartmentDetails;
}
}

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;

Creating a custom Angular filter with TypeScript

I'm trying to work out the best way of creating a custom Angular Filter with TypeScript.
All the code samples I see use something like:
myModule.filter( "myFilter", function()
{
return function( input )
{
// filter stuff here
return result;
}
}
... which works, but seems messy as I want to keep all my filter code separate. So I want to know how to declare the filter as a separate file (eg filters/reverse-filter.ts) so I can create it like:
myModule.filter( "filterName", moduleName.myFilter );
... the same way you would for Controllers, Services etc.
The documentation for TS and Angular together seems pretty thin on the ground, especially where filters are concerned - can anyone help out?
Cheers!
Functions can be exported from modules like this:
module moduleName {
export function myFilter()
{
return function(input)
{
// filter stuff here
return result;
}
}
}
then outside the module:
myModule.filter("filterName", moduleName.myFilter);
Then it would then be possible to do things like automatically register all of the filters defined in the moduleName module by iterating over its public properties.
Maybe too late but can be useful for someone else.
module dashboard.filters{
export class TrustResource{
static $inject:string[] = ['$sce'];
static filter($sce:ng.ISCEService){
return (value)=> {
return $sce.trustAsResourceUrl(value)
};
}
}
}
dashboard.Bootstrap.angular.filter('trustAsResourceUrl',dashboard.filters.TrustResource.filter);
To explain the last line:
dashboard.Bootstrap.angular.filter('trustAsResourceUrl',dashboard.filters.TrustResource.filter);)
I will add a piece of code, wich represents my Bootstrap class, so you can understand it.
module dashboard {
export class Bootstrap {
static angular:ng.IModule;
static start(){
Bootstrap.angular = angular.module('EmbApp', dashboard.Bootstrap.$inject);
}
}
}
//run application
dashboard.Bootstrap.start();
If you need more information about how it works, you can checkout my own TypeScript/AngularJS/Less structure here
Here's an example using the injector to get dependencies into your filter. This one gets injected with the $filter service.
export class CustomDateFilter {
public static Factory() {
var factoryFunction = ($filter: ng.IFilterService) => {
var angularDateFilter = $filter('date');
return (theDate: string) => {
return angularDateFilter(theDate, "yyyy MM dd - hh:mm a");
};
};
factoryFunction.$inject = ['$filter'];
return factoryFunction;
}
}
// and in the bootstrap code:
app.filter('customDate', your.module.CustomDateFilter.Factory());
You should use something like this to inject dependencies
myModule.filter('filterName', ['$http', moduleName.myFilter]);
You can create a filter using class with a static function.
export class FilterClass
{
static id = "FilterId"; //FilterName, use while consume
/*#ngInject*/
public static instance() { //static instance function
let dataFilter = () => {
let filteredObject = () => {
//filter logic
return filteredData;
}
return filteredObject;
}
return dataFilter;
}
}
//Module configuration
angular.module(myModule).filter(FilterClass.id, FilterClass.instance());
Consume this filter in the controller using below way.
let FilterFun:any = this.$filter('FilterId');
let Filteroutput = FilterFun();

Resources