Angular 1.5 components : Component based application architecture - angularjs

According to the Angular 1.5 documentation components should only control their own View and Data.
Instead of changing properties of objects passed to the component, a component should create an internal copy of the original data and use callbacks to inform the parent component when this copy has changed.
In this plunk I created a small demo illustrating my problem.
interface IStudent {
id: number,
name: string;
}
/* SERVICE: StudentService */
public class StudentsService {
static $inject = ['$q'];
constructor(private $q: ng.IQService) {
}
public getStudents() : ng.IPromise<IStudent[]> {
return this.$q.when([
{ id: 1, name: 'Adam' },
{ id: 2, name: 'Ben' }
}]);
}
}
/* COMPONENT: student */
class StudentsComponent implements ng.IComponent {
public template = `<student-list on-selected="$ctrl.onSelected(student)"></student-list>
<edit-student student="$ctrl.student" ng-show="$ctrl.student" on-changed="$ctrl.copyChanged(copy)"></edit-student>`;
public controller = StudentsController;
}
class StudentsController {
private student: IStudent;
protected onSelected(student: IStudent) {
this.student = student;
}
protected copyChanged(copy: IStudent) {
this.student.name = copy.name;
}
}
/* COMPONENT: student-list */
class StudentListComponent implements ng.IComponent {
public template = '<ul><li ng-repeat="student in $ctrl.students"><a ng-click="$ctrl.onClick(student)">{{ student.name }}</a></li></ul>';
public controller = StudentListController;
public bindings : any = {
onSelected: '&'
}
}
class StudentListController {
protected students: IStudent[];
static $inject = ['studentsService'];
constructor(private studentsService: StudentsService) {
}
public $onInit() {
this.studentsService.getStudents().then(data => this.students = data);
}
protected onClick(student: IStudent) {
this.onSelected({ student: student });
}
}
/* COMPONENT: edit-student */
class EditStudentComponent implements ng.IComponent {
public template = `<form class="form">
<div class="input-group">
<label for="#" class="control-label">Original</label>
<input type="text" class="form-control" ng-model="$ctrl.student.name" readonly>
</div>
</form>
<form class="form">
<div class="input-group">
<label for="#" class="control-label">Copy</label>
<input ng-change="$ctrl.changed()" type="text" class="form-control" ng-model="$ctrl.copy.name">
</div>
</form>`;
public controller = EditStudentController;
public bindings :any = {
student: '<',
onChanged: '&'
};
}
class EditStudentController {
protected copy: IStudent;
public $onInit() {
console.log('EditStudentComponent.$onInit', this.student);
}
public $onChange() {
console.log('EditStudentComponent.$onChange', this.student);
this.copy = angular.copy(this.student);
}
protected changed() {
console.log('EditStudentController.changed', this.copy);
this.onChanged({ copy: this.copy });
}
}
/* Bootstrap */
angular
.module('app', [])
.component('students', new StudentsComponent())
.component('studentList', new StudentListComponent())
.component('editStudent', new EditStudentComponent())
.service('studentsService', StudentsService)
;
angular.bootstrap(document, ['app']);
I have a list iterating over students. When the user selects a student, a textbox is shown in which the user can change the name of the student. Whenever the name changes, this change is propagated to the parent component which updates the list.
The problem is that after selecting a student in the list, the edit-user component is not initialized and still shows the name of the copy created when the component was created (null).
Can someone tell me how to fix this plunk such that, when clicking a student in the list, the edit component gets initialized with a copy of the selected student?
Edit: changed the plunk, as I accidentally removed the script tag instead of the style tag.

I thought this plunk represented my problem, but alas it didn't. The plunk didn't work because I implemented $onChange instead of $onChanges. I fixed the plunk such that it works as expected.
The cause of my original problem was a completely different one. In my business application I used another component with a ng-transclude directive around my edit component, like this:
<modal-editor>
<edit-student data="$ctrl.data">
<edit-student>
</modal-editor>
As the edit-student component was defined in the isolated scope of the modal-editor component, it didn't receive any changes made to the data variable in the outer scope (but somehow it could still access the data from this outer scope).
After modifying the modal-editor component such that it passed the data to the child component, everything worked as expected:
<modal-editor data="$ctrl.data">
<edit-student data="$ctrl.data">
<edit-student>
</modal-editor>

Related

Dynamic array with angular

Basically I have an api that returns data when I do a search, I push them into an array and I display them by doing an ngFor in my html.
When I want to do a new search it's the same function that is called, but the html is not updated while I get new data.
It always appears the old data recovered the first time.
To search, i used this code :
SearchBar.component.ts
export class RechercheToutComponent implements OnInit {
searchInput = new FormControl('');
constructor(
private router: Router,
private recherche: RechercheComponent
) { }
ngOnInit(): void {
}
search() {
if(this.router.url.match(/recherche.*!/)){
this.recherche.searchResult(this.searchInput.value)
}else{
this.router.navigate(['/recherche'], {queryParams: {search: this.searchInput.value}}).then(r => this.recherche.searchResult(this.searchInput.value))
}
}
}
SearBar.component.html
<form class="catalogue-search-form" (ngSubmit)="search()">
<div class="search-bar">
<input type="text"
[formControl]="searchInput"
placeholder="Rechercher dans Intra"
/>
<button type="submit" class="text-button">
<mat-icon>search</mat-icon>
</button>
</div>
</form>
Search.component.ts
export class RechercheComponent implements OnInit {
searchParam: any;
results$: Observable<Array<any>>;
isResultLoading: boolean = true;
constructor(
private route: ActivatedRoute,
private http: HttpClient
) {
}
ngOnInit(): void {
this.route.queryParams.subscribe(params => {
this.searchParam = params['search']
});
this.searchResult(this.searchParam);
}
searchResult(searchParam) {
this.http.get(`${environment.apiUrl}/Recherchetout.php`, {params: {search: searchParam}}).subscribe(
(data: Array<any>) => {
this.results$ = of(data)
this.isResultLoading = false;
}
);
}
}
Search.component.html
<div class="recherche">
<div class="spinner-search" *ngIf="isResultLoading">
<app-spinner></app-spinner>
</div>
<div class="content" *ngIf="!isResultLoading">
<div *ngFor="let oui of results$ | async">
<div *ngIf="oui.produit != undefined">
{{ oui.produit.pdf }}
</div>
</div>
</div>
</div>
I tried to create observables but it didn't work, with a simple array too.
So my question is: Why is my data not updating on my html?
And how to do it ?
Sorry in advance for the mistakes, or for the disastrous code I begin in angular
you are injecting RechercheComponent inside the SearchBar component, angular will create different instance than the one used on the UI.
to send data between multiple components create a parent component and use it to allow communication between the two components (use the Input and Output attributes).
make your system navigate to the parent then
Parent.Component.ts
export class ParentComponent {
data: any[];
isResultLoading = false;
updateData(data: any[]) {
this.data = data
}
}
parent.component.html
<app-search-bar (change)="dataChange($event)" [(isResultLoading)]="isResultLoading"></app-search-bar>
<app-search [data]="data" *ngIf="!isResultLoading"></app-search>
update your Search.component.ts
//add outputs and inputs
#Output() change = new EventEmitter<any[]>(); //make sure to import EventEmitter from #angular/core
#Input() isResultLoading : boolean
// update searchResult subscription function
searchResult(searchParam) {
this.isResultLoading = true;
this.http.get(`${environment.apiUrl}/Recherchetout.php`, { params: { search: searchParam } }).subscribe(
(data: Array<any>) => {
this.change.emmit(data);
this.isResultLoading = false;
}
);
}
and finally instead of having observable result$ inside Search.component.ts
replace it with #Input() data:Any[]
Assuming you want to refresh the search every time a URL change, update you ngOnInit by moving this.searchResult(this.searchParam); inside the subscribe method
ngOnInit(): void {
this.route.queryParams.subscribe(params => {
this.searchParam = params['search']
this.searchResult(this.searchParam);
});
}
with this searchResult will be called every time the router parameters get change instead of updating for the first time ngOnInit get called

How to dynamically bind a custom AngularJS modal?

For an educational side-project I am working on, I want to avoid using AngularJS Material Design, UI Bootstrap, or any custom libraries that provide modal functionality.
However, I've hit a snag. I've created a service that is supposed to manage and dynamically create modals. It provides an open function that accepts a spec object, which it then reproduces in the DOM.
What this code actually does:
1. The modal is correctly appended to the DOM.
2. The modal controller's $onInit function fires.
What this code does not do:
1. Bind the $ctrl.message property in the markup to the instance of the controller that we know starts.
Normally, I would ask my question after providing code, however there's a good bit of code required to reproduce this problem (it's below, sans some AngularJS boilerplate.) As that's the case, though, here's my question:
In what way can I get the modals being spun off by this service to properly bind their contents to their given controller?
What I've tried:
As you can see in ModalService.bindModalDiv, I've tried a few avenues of thought, mostly using $compile. Yet, $compile and the resulting link function don't actually seem to be binding the new DOM elements to Angular.
I've tried using $controller to explicitly bind the new scope being generated to the someModalCtrl being instantiated, but that doesn't seem to help at all.
Because I can hit breakpoints on the someModalCtrl, and see the console.log message I used as a sanity check, I think I'm misunderstanding how exactly I'm supposed to bind the new DOM elements to Angular. I'm sure I'm missing something basic that I've managed to forget about or disregard, somehow.
One more note:
I'm sure my problems with getting the modal to bind to AngularJS properly aren't the only problems present. Please remember, I'm doing this partially as a learning excersize; if y'all can help me figure out my modal problem, I'll keep on doing my due diligence and hunting down the flaws I've doubtless built into this approach. Therefore, if you see something that's not a modal problem, it's OK to draw my attention to it, but I won't rewrite the question to fix whatever you find - unless it's absolutely essential that I do. As an example - I know that ModalService.open has some issues in how I'm implementing the promise setup. A $rootScope.$watch is probably more reasonable.
modalSvc.ts:
export interface IModalSpecObject {
parent?: string | Element | JQuery;
templateURL: string
controller: string;
controllerAs?: string;
data: object;
}
export class ModalInstance {
public isOpen: boolean = true;
public returnData: object = null;
public element: JQLite = null;
public $parent: JQuery = null;
public constructor(
public specObject: IModalSpecObject
) {
}
public close(returnData: object): void {
if (this.element)
this.element.remove();
this.isOpen = false;
this.returnData = returnData;
}
}
export class ModalService {
public pollRate: number = 250;
public instance: ModalInstance = null;
public static $inject: string[] = [
'$q', '$rootScope', '$compile', '$controller'
];
public constructor(
public $q: ng.IQService,
public $rootScope: ng.IRootScopeService,
public $compile: ng.ICompileService,
public $controller: ng.IControllerService
) {
}
public open(specObject: IModalSpecObject): ng.IPromise<{}> {
if (this.instance && this.instance.isOpen)
this.instance.close(null);
this.instance = new ModalInstance(specObject);
const $parent: JQuery = this.setParent(specObject);
const modalDiv: JQLite = this.buildModal(specObject);
this.bindModalDiv(modalDiv, $parent);
const result: ng.IPromise<{}> = this.$q((resolve) => {
setInterval(() => {
if (!this.instance.isOpen) {
resolve(this.instance.returnData);
}
}, this.pollRate);
});
return result;
}
private buildModal(specObject: IModalSpecObject): JQLite {
const modalDiv: JQLite = angular.element('<div/>');
modalDiv.addClass('modal');
const $modalPanel: JQuery = $('<div/>');
$modalPanel.addClass('modal-panel');
// Inject HTML template...
$modalPanel.load(specObject.templateUrl);
// Set up the angular controller...
const controllerAs: string = specObject.controllerAs
? specObject.controllerAs
: '$ctrl';
$modalPanel.attr('ng-controller', `${specObject.controller} as ${controllerAs}`);
modalDiv.append($modalPanel);
this.instance.element = modalDiv;
return modalDiv;
}
private setParent(specObject: IModalSpecObject): JQuery {
let $parent: JQuery;
if(!specObject.parent)
$parent = $(document);
else if (typeof specObject.parent === "string"
|| specObject.parent instanceof Element)
$parent = $(specObject.parent);
else if (specObject.parent instanceof jQuery)
$parent = specObject.parent;
else
$parent = $(document);
this.instance.$parent = $parent;
return $parent;
}
// !!!! !!!! I suspect this is where my problems lie. !!!! !!!!
private bindModalDiv(modalDiv: JQLite, $parent: JQuery): void {
const newScope: ng.IScope = this.$rootScope.$new(true);
// Try #1: Bind generated element to parent...
//$parent.append(this.$compile(modalDiv)(newScope));
// Try #1a: Generate bindings, then append to parent...
//const element: JQLite = this.$compile(modalDiv)(newScope);
//$parent.append(element);
// Try #2: Bind element to parent, then generate ng bindings...
//$parent.append(modalDiv);
//this.$compile(modalDiv)(newScope);
// Try #3: Well, what if we bind a controller to the scope?
const specObject: IModalSpecObject = this.instance.specObject;
const controllerAs: string = specObject.controllerAs
? specObject.controllerAs
: '$ctrl';
this.$controller(`${specObject.controller} as ${controllerAs}`, {
'$scope': newScope
});
const element = this.$compile(modalDiv)(newScope);
$parent.append(element);
}
}
angular
.module('app')
.service('modalSvc', ModalService);
SomeController.ts:
SomeController.ts pretty much just controls a button to trigger the modal's appearance; I've not included the markup for that reason.
export class SomeController {
public static $inject: string[] = [ 'modalSvc' ];
public constructor(
public modalSvc: ModalService
) {
}
public $onInit(): void {
}
public openModal(): void {
const newModal: IModalSpecObject = {
parent: 'body',
templateUrl: '/someModal.html',
controller: 'someModalCtrl',
data: {
'message': 'You should see this.'
}
};
this.modalSvc.open(newModal)
.then(() => {
console.log('You did it!');
});
}
}
angular.module('app').controller('someCtrl', SomeController);
someModal.html:
<div class="modal-header">
Important Message
</div>
<!-- This should read, "You should see this." -->
<div class="modal-body">
{{ $ctrl.message }}
</div>
<!-- You should click this, and hit a breakpoint and/or close the modal. -->
<div class="modal-footer">
<button ng-click="$ctrl.close()">Close</button>
</div>
someModal.ts:
export class SomeModalController {
public message: string = '';
public static $inject: string[] = [ 'modalSvc' ];
public constructor(
public modalSvc: ModalService
) {
}
public $onInit(): void {
console.log('$onInit was triggered!');
this.message = this.modalSvc.instance.specObject.data['message'];
}
public close(): void {
this.modalSvc.instance.close(null);
}
}
angular
.module('app')
.controller('someModalCtrl', SomeModalController);
I figured out where I went wrong - I needed to use $().load()'s callback. JQuery load is asynchronous, which meant that $compile was working correctly; however, the HTML in my modal partial wasn't loaded by the time $compile had done its job, thus the unbound HTML.
A slight modification of my ModalService got around this, though.
Revised fragment of ModalSvc.ts:
// This is just a convenience alias for void functions. Included for completeness.
export type VoidFunction = () => void;
// ...
public open(specObject: IModalSpecObject): ng.IPromise<{}> {
if (this.instance && this.instance.isOpen)
this.instance.close(null);
this.instance = new ModalInstance(specObject);
const $parent: JQuery = this.setParent(specObject);
// open already returned a promise before, we just needed to return
// the promise from build modal, which in turn sets up the true
// promise to resolve.
return this.buildModal(specObject)
.then((modalDiv: JQLite) => {
this.bindModalDiv(modalDiv, $parent);
const result: ng.IPromise<{}> = this.$q((resolve) => {
// Also, side-note: to avoid resource leaks, always make sure
// with these sorts of ephemeral watches to capture and release
// them. Resource leaks are _no fun_!
const unregister: VoidFunction = this.$rootScope.$watch(() => {
this.instance.isOpen
}, () => {
if (! this.instance.isOpen) {
resolve(this.instance.returnData);
unregister();
}
});
});
return result;
});
}
private buildModal(specObject: IModalSpecObject): ng.IPromise<{}> {
const modalDiv: JQLite = angular.element('<div/>');
modalDiv.addClass('modal');
this.instance.element = modalDiv;
const $modalPanel: JQuery = $('<div/>');
$modalPanel.addClass('modal-panel');
// By wrapping $modalPanel.load in a $q promise, we can
// ensure that the modal is fully-built before we $compile it.
const result: ng.IPromise<{}> = this.$q((resolve, reject) => {
$modalPanel.load(specObject.templateUrl, () => {
modalDiv.append($modalPanel);
resolve(modalDiv);
});
});
return result;
}
private setParent(specObject: IModalSpecObject): JQuery {
let $parent: JQuery;
if(!specObject.parent)
$parent = $(document);
else if (typeof specObject.parent === "string"
|| specObject.parent instanceof Element)
$parent = $(specObject.parent);
else if (specObject.parent instanceof jQuery)
$parent = specObject.parent;
else
$parent = $(document);
this.instance.$parent = $parent;
return $parent;
}
private bindModalDiv(modalDiv: JQLite, parent: JQLite): void {
// parent should be a JQLite so I can use the injector() on it.
parent.injector().invoke(['$rootScope', '$compile', ($rootScope, $compile) => {
const newScope: ng.IScope = $rootScope.$new(true);
this.$controller(this.getControllerAsString(), {
'$scope': newScope
});
const element: JQLite = $compile(modalDiv)(newScope);
parent.append(element);
}]);
}
private getControllerAsString(): string {
const specObject: IModalSpecObject = this.instance.specObject;
const controllerAs: string = specObject.controllerAs
? specObject.controllerAs
: '$ctrl';
return `${specObject.controller} as ${controllerAs}`;
}
I figured this out by going back and doing the step-by-step engineering. I first ensured that $compile was working by creating an element whose contents were {{ 2 + 2 }}, compiling it, then injecting it. When I saw 4 added to my page, I knew that the compile-then-inject aspect of the program worked just fine.
From there, I started building up the modal's construction, and found it working flawlessly...up until I got to jQuery load. When I read the documentation, I saw the error of my ways.
TL;DR: Read the friendly manual!

Angular 2 keep input form value in bind with the model using getter and setter

I would like to bind an input value to the model using getter and setters. In this way i can prevent and/or manipulate the input's value while writing inside it.
For example i want the prevent numbers inside an input box. So, if write 'abc' all is ok, then if I start writing a number nothing should happen (to the model and to the input's value). The issue is that with the following code i'm able to write anything inside the input box (but the model it's correct). This means that the input box value is not really representing my model.
NOTE: The reason beyond this questions is that I want to use my models to validate forms, preventing for example specific characters. I would like to not use reactive forms as i want to keep my validations inside my models not components. Also note that in a real scenario i would have a UserModel class with inside name and other fields with their validations.
#Component({
selector: 'my-app',
template: `
<div>
<h2><input type="text" [(ngModel)]="name"> {{name}}</h2>
</div>
`,
})
export class App {
_name:string = 'ss';
constructor() {
}
// In real scenario those 2 methods are in a separate class UserModel
get name() {
return this._name;
}
set name() {
if ((new RegExp(/^[a-zA-Z]*$/).test(val))) {
this._name = val;
}
}
}
If you manipulate the value in the setter, this can cause issues with change detection, so that ngModel doesn't pick up the changes and doesn't update the <input>
To work around you can use
export class App {
_name:string = 'ss';
constructor(private cdRef:ChangeDetectorRef) {}
get name() {
return this._name;
}
set name(value:String) {
this._name = value + 'x';
this.cdRef.detectChanges()
}
}
if you reset the value to the previous value, you might need to pass an artificial different value first, otherwise change detection won't detect a change and even detectChanges() won't update the input.
set name(value:String) {
var oldVal = this._name;
this._name = null;
this.cdRef.detectChanges()
this._name = oldVal;
this.cdRef.detectChanges()
}
Based on #Günter Zöchbauer answer i made a workaround. It's not definitive and could be more abstract, but for now it's ok.
export class App implements OnInit {
#Input() userModel: UserModel = null;
public _vm;
constructor(private _changeDetectionRef: ChangeDetectorRef) {
}
/**
* Initalize view model, it's important to keep names specular
*/
ngOnInit() {
this._vm = {
name: this.userModel.name,
surname: this.userModel.surname,
};
}
/**
* Helper for avoid detectchanges inside the modal, and reduce boilerplate. We could also ad an interface/type of the possibile field value, ie type fieldT= 'name' | 'surname';
* #param field
* #param val
*/
protected updateModel(field, val: string): void {
this._vm[field] = null;
this._changeDetectionRef.detectChanges();
this.userModel[field] = val;
this._vm[field] = this.userModel[field];
this._changeDetectionRef.detectChanges();
}
}
In userModel:
....
public get name(): string {
return this.name';
}
public set name(val: string) {
if ((new RegExp(/^[a-zA-Z]*$/).test(val))) {
this.name = val;
}
}
In template:
<input type="text" name="userName" [ngModel]="_vm.name" (ngModelChange)="updateModel('name', $event)">
You can use (ngModelChange) and [ngModel] to test the content of your model upon change.
As you can see in this Plunker the model wont change if it is not valid.
#Component({
selector: 'my-app',
template: `
<div>
<h2><input #input type="text" [ngModel]="name" (ngModelChange)='valid(input.value)'> {{name}}</h2>
</div>
`,
})
export class App {
name:string = 'ss';
constructor() {
}
valid(value){
if(value){ //<--- Your test here
this.name = value;
}
}
}

Add Property to my NPM Module

This might be answered, but damn if I can find it . I am creating a module, and its working, the issue is I want to assign a property to another property on my module.
so in angular 2 (with ng-module) I have created a simple panel
<simple-panel name="MyPanel"></simple-panel>
I have it working great, the issue is I want to assign a property to the name Property now and I have no idea what is the best way to do this.
so I would like to return {{MyPanel.thisProperty}} for use on the page where I am calling the tag.
here is a sample of what I am doing, stripped down for this question
here is my simple-panel.ts
import {Component,NgModule,ModuleWithProviders, Directive, Input} from '#angular/core';
/**
* Content of the edit panel.
*/
#Directive({
selector: 'simple-panel-edit-panel-content'
})
export class SimplePanelEditPanelContent {}
#Component({
selector: 'simple-panel',
templateUrl: 'simple-panel.html',
styleUrls: ['simple-panel.css'],
encapsulation: ViewEncapsulation.None
})
export class SimplePanel{
private _name: string;
private _announceedit: boolean = false;
private _buttonname: string = 'edit';
/** This sets the Name as a variable that can be used. */
#Input()
get name(): string { return this._name; }
set name(value) { this._name = value; }
/**Sets the Edit Announcement */
#Input()
get editannounce(): boolean { return this._announceedit; }
set editannounce(value: boolean) {
if (!value) {
this._announceedit = true;
this._buttonname = 'search';
}else{
this._announceedit = false;
this._buttonname = 'edit';
}
}
}
#NgModule({
exports: [SimplePanel,SimplePanelEditPanelContent],
declarations: [SimplePanel,SimplePanelEditPanelContent],
})
export class SimplePanelComponent {
static forRoot(): ModuleWithProviders {
return {
ngModule: SimplePanelComponent,
providers: []
};
}
}
here is the simple-panel.html
<md-card>
<md-card-title-group>
<button md-raised-button (click)="editannounce=editannounce;"><md-icon>{{ _buttonname }}</md-icon></button>
</md-card-title-group>
<md-card-content>
<ng-content select="simple-panel-edit-panel-content"></ng-content>
</md-card-content>
<md-card-actions>
<button md-raised-button (click)="editannounce = editannounce"><md-icon>save</md-icon> SAVE</button>
</md-card-actions>
</md-card>
when someone uses the module, a panel is created with a button
when someone clicks the button I can access the variable within the template above, but what I want to do is actually access a variable that is used on the page itself where they call the module to use. it would be nice to have it named MyPanel.announceedit or MyPanel.editable as an example, but the main thing is that a variable is created, and watched, when it changes it passes it back up to where the module is bieng used and allows user the ability to access it within the content area, so if they added an input and wanted to see if the button was clicked to set the readOnly attribute they could. Hopefully this makes more sense.
If you write it like
<simple-panel [name]="MyPanel"></simple-panel>
in the component that includes this html, you can access/set MyPanel with a simple this.MyPanel.
And in your SimplePanel component
#Input() name;
...
this.name = "something";
is again all you need to set and get that field.

AngularJS + TypeScript: this always null in filter function

I am just starting with Angular, and this problem has already been bugging me for hours:
Initially, I wrote a small test application in Javascript only. Now I wanted to switch to TypeScript, which basically works all fine. Except one thing: I created a custom timerange filter, which I just cannot get to work in the Typescript version of my app.
index.html:
<div ng-controller "MyController as ctrl">
<input id="date_from_filter" ng-model="ctrl.dateFromFilter" />
<input id="date_to_filter" ng-model="ctrl.dateToFilter" />
<table>
<tr ng-repeat="event in ctrl.events | filter:ctrl.rangeFilter">
<td>{{event.id}}</td>
<td>{{event.timestamp}}</td>
<td>{{event.value}}</td>
</tr>
</div>
controller.js
class MyController {
dateFromFilter: string;
dateToFilter: string;
constructor(public helperService: app.services.HelperService) { }
rangeFilter(event) {
// Compare mechanism, accessing this.dateFromFilter and this.dateToFilter and using helperService
// But: `this` is always null in here
}
}
Can anyone give me a hint on how I can do this? I think I got that this cannot be accessed as the scope is different inside the rangeFilter method. But what are my options?
Thank you!
UPDATE:
I am thrown a TypeError: Unable to get property 'dateFromFilter' of undefined or null reference (*.js:30:33)
Here is the compiled Javascript:
MyController.prototype.rangeFilter = function (event) {
var x = new Date(this.dateFromFilter);
//... more stuff..
}
I have tested the below code in my current angular TS project and it seems to work fine, if you have any questions, drop me a comment. If you need further info on what your rangeFilter() function is doing, put a debugger; on the first line of your rangeFilter() function.
class MyController {
public dateFromFilter: string;
public dateToFilter: string;
public static $inject: string[] = ["$scope"];
constructor(public $scope: ng.IScope, public helperService: app.services.HelperService) {}
public rangeFilter(event) {
this.dateFromFilter // will be null until set
this.dateToFilter // will be null until set
this.$scope // will give you the scope of the controller
}
}

Resources