Accessing base member from derived class in function - javascript

I have created Angular2 + Typescript project. I have there alot of tables so I want to have something like base component for that.
There is my base component:
export abstract class ManagementComponent<T> implements BaseComponent {
protected selectedItem: T;
protected items: Array<T>;
}
Now there is my child component. I would like to get all items from http and then assign it into base class
export class CompetencesComponent extends ManagementComponent<Competence> implements OnInit {
thisField: string;
constructor(private service: CompetencesService) {
super();
}
ngOnInit(): void {
this.getCompetences();
}
private getCompetences() {
this.service.getCompetences().subscribe(function (competences: Array<Competence>) {
this.thisField // ok
this.items // not ok
})
}
}
Any idea how I can access base fields from subscribe methods?

Currently I'd expect that you wouldn't be able to reference either thisField or items, because you should be losing the this context inside your subscription function.
You can switch to an arrow function to retain context:
this.service.getCompetences().subscribe((competences: Array<Competence>) => { ... }

You can set list of competencies to parent class as follow:
private getCompetences() {
var self = this;
this.service.getCompetences().subscribe(function (competences: Array<Competence>) {
this.thisField // ok
self.items = competences; // not ok
})
}
The reason you are unable to access items property through this binding is the scope. Inside callback this binding is bound to something else and you loose the context.

Related

Unable to access/get value of abstract (base) class properties from derived class - TypeScript

I have 2 classes - one is abstract and another class is extending it. In ABSTRACT class I have some public/protected properties which ARE initialized in constructor.
Let it be abstract Parent and Child extends Parent
Questions:
Why, when I am trying to get value of the properties of abstract class like: super.somePropertyOfParent it is always UNDEFINED, but when I call it like: this.somePropertyOfParent it HAS value? Logically, super constructor is always called first, so these fields should be initialized first of all.
I have 2 BehaviourSubjects (countryValue, languageValue) in my Parent abstract class, which are initialized with some 'initial value' in constructor. In Child class in OnInit method (which obviously called after Parent constructor) I am subscribing to Parent's BehaviourSubjects like: this.countryValue.subscribe(...) and it receives the 'INITIAL' value. Then in Parent's class ngOnChange method calls subject.next(...), but Child doesn't receive new value...why?
P.S. if make BehaviourSubject properties STATIC and refer to the ClassName.property - everything works fine.
Please see code below:
#Directive()
export abstract class IbCustomElementComponent implements OnChanges{
#Input('data-country') country = '';
#Input('data-language') language = '';
public countryValue:BehaviorSubject<string>;
public languageValue:BehaviorSubject<string>;
protected constructor(public translateService: TranslateService) {
this.countryValue = new BehaviorSubject<string>('initial');
this.languageValue = new BehaviorSubject<string>('initial');
}
abstract customElementReady(changes: SimpleChanges): void;
ngOnChanges(changes: SimpleChanges) {
if (this.country && this.language) {
this.translateService.use(this.country.toLocaleLowerCase() + '-' + this.language);
this.customElementReady(changes);
this.countryValue.next(this.country);
this.languageValue.next(this.language);
}
}
}
export class CustomerCardsComponent extends IbCustomElementComponent implements OnInit {
displayedColumns: string[] = ['fieldName', 'value'];
CARD_DATA: CardData[][] = [];
dataSource = this.CARD_DATA;
cards: Card[] = [];
currentCustomer : Customer = new Customer();
constructor(private customerListService: CustomerListService, public override translateService: TranslateService) {
super(translateService);
}
ngOnInit(): void {
this.countryValue.subscribe(c=>{
this.currentCustomer.bic = Bic[c.toUpperCase()];
if(this.currentCustomer.bic){
this.getCustomerCards(this.currentCustomer)
}
})
}
}
Firstly, the reason you can't call super.somePropertyOfParent is because your abstract class isn't initialised separately from your derived class — your derived class simply inherits all of the properties from the abstract class. That is why you call this and not super.
For the ngOnChanges side of things, I believe what's happening is your abstract class' method isn't called because, as far as I understand, Angular components/directives are annoying when it comes to inherited lifecycle hooks. I know I've had issues with OnInit and OnDestroy in the past.
I would try implementing OnChanges in your derived class as follows:
ngOnChanges(changes: SimpleChanges) {
super.ngOnChanges(changes);
}
Thus you only use super when referring to the parent class' implementation of a method, and not for any properties which your child class automatically inherits.

Inheriting Instance Methods with Static References in JavaScript/TypeScript

👋 The following examples use access modifiers from TypeScript, but I think the question should also be relevant for JavaScript developers. Let's say I have some parent class, where I define some necessary members and an implementation of a shared method:
// ParentClass.ts
export default class ParentClass {
private static voo;
private bar;
constructor(bar) {
this.bar = bar;
}
private commonMethod(baz) {
doSomething(ParentClass.voo);
doSomethingElse(this.bar, baz);
}
}
And then I have some child class, that inherits this behavior:
// ChildClass.ts
export default class ChildClass extends ParentClass {
voo = "Jake";
constructor(bar) {
super(bar)
}
public uniqueMethod(ter) {
doAnotherThing(this.bar);
this.commonMethod(ter);
}
}
Of course, when I call commonMethod() inside of ChildClass.uniqueMethod(), it will reference the value of ParentClass.voo, which is undefined. What I would like to happen is that each inheriting child class uses the exact same implementation of that method, but it references the static member of the child class itself. So when I call ChildClass.uniqueMethod(), commonMethod() will use the value from ChildClass.voo() rather than the parent equivalent.
One sidesteps this issue entirely by just making voo an instance member, rather than a static member, but let's say that you have some scenario where a static voo is more useful in other ways.
Is such a solution readily available? I've posted the solution that I'm currently using as a reply to this question, but I can't help but think there's a more direct solution out there.
This is what I currently have, where I'm attaching an instance accessor, and adjusting the commonMethod() to use that accessor:
// ParentClass.ts
export default class ParentClass {
private static _voo;
private bar;
constructor(bar) {
this.bar = bar;
}
public get voo() {
return ParentClass._voo;
}
private commonMethod(baz) {
doSomething(this.voo);
doSomethingElse(this.bar, baz);
}
}
And then inheriting classes override that accessor:
// ChildClass.ts
export default class ChildClass extends ParentClass {
_voo = "Jake";
constructor(bar) {
super(bar)
}
public override get voo(){
return ChildClass._voo
}
public uniqueMethod(ter) {
doAnotherThing(this.bar);
this.commonMethod(ter);
}
}
This route doesn't seem very elegant to me, so I'd be very interested to hear feedback or suggestions about better ways to do this.

httpClient.get is undefined when using dynamic function/observable references

So I asked a question a few days ago and got some headway on a solution, however now I'm stuck at another wall I'm unsure how to get over.
I have two parent components, a shared view/component with an extended base component, and a service all hooked together. The objective is to use the two parent components to drive what data is shown within the shared component. The two parent components use references to service methods passed into the shared component to get the data.
I've reached an issue where my http.get is always undefined no matter what I try. I've instantiated it like I do in my other services but I've had no luck. I suspect this is caused by how i pass in my service references. Code below:
Parent Component Code:
// PARENT COMPONENT
myData$: Observable<myType>;
searchMethod: Function;
constructor(private myService){
this.myData$ = this.myService.myData$;
this.searchMethod = this.myService.searchData;
}
// PARENT COMPONENT HTML
<app-shared-component
[myData$] = "myData$"
[searchMethod]="searchMethod">
</app-shared-component>
Shared Component Code:
export class MySharedComponent extends BaseComponent<MyType> implements OnInit {
#Input() myData$: Observable<myType>;
#Input() searchMethod: Function;
constructor() { super(); }
ngOnInit(): void {
this.data$ = this.myData$;
}
search(): void {
this.searchMethod().subscribe(//do something);
}
Base Component Code:
#Input data$: Observable<T>;
ngOnInit(): void {
this.data$.subscribe((response: T) => //do something);
super.ngOnInit();
}
Service Code:
private myDataSubject = new BehaviorSubject<MyType>(new MyType());
get myData$(): Observable<MyType> {
return this.myDataSubject.asObservable();
}
constructor(private http: HttpClient) { }
searchData(): Observable<void> {
return new Observable<void>(observer => {
this.http.get<MyType>(
'http://myuri'
).subscribe(
response => {
// do something
},
() => observer.error(),
() => observer.complete()
);
});
}
It looks like you're losing the context of your service when you set this.searchMethod = this.myService.searchData in your parent component. It should work if you change searchData() { to an arrow function: searchData = (): Observable<void> => {.

Two Way Binding on an Angular 2+ Component

I have an Ionic application where I have created a component to show some data of an object. My problem is that when I update the data in the parent that hosts the component the data within the component does not update:
my-card.component.ts
#Component({
selector: 'my-card',
templateUrl: './my-card.html'
})
export class MyCard {
#Input('item') public item: any;
#Output() itemChange = new EventEmitter();
constructor() {
}
ngOnInit() {
// I do an ajax call here and populate more fields in the item.
this.getMoreData().subscribe(data => {
if (data.item){
this.item = data.item;
}
this.itemChange.emit(this.item);
});
}
}
my-card.html
<div class="comment-wrapper" *ngFor="let subitem of item.subitems">
{{subitem.title}}
</div>
And in the parent I use the component like this:
<my-card [(item)]="item"></my-card>
And the ts file for the parent:
#IonicPage()
#Component({
selector: 'page-one',
templateUrl: 'one.html',
})
export class OnePage {
public item = null;
constructor(public navCtrl: NavController, public navParams: NavParams) {
this.item = {id:1, subitems:[]};
}
addSubItem():void{
// AJAX call to save the new item to DB and return the new subitem.
this.addNewSubItem().subscribe(data => {
let newSubItem = data.item;
this.item.subitems.push(newSubItem);
}
}
}
So when I call the addSubItem() function it doesnt update the component and the ngFor loop still doesnt display anything.
You are breaking the object reference when you are making the api request. You are assigning new value, that is overwriting the input value you get from the parent, and the objects are no longer pointing to the same object, but item in your child is a completely different object. As you want two-way-binding, we can make use of Output:
Child:
import { EventEmitter, Output } from '#angular/core';
// ..
#Input() item: any;
#Output() itemChange = new EventEmitter();
ngOnInit() {
// I do an ajax call here and populate more fields in the item.
this.getMoreData(item.id).subscribe(data => {
this.item = data;
// 'recreate' the object reference
this.itemChange.emit(this.item)
});
}
Now we have the same object reference again and whatever you do in parent, will reflect in child.
If the getMoreData method returns an observable, this code needs to look as follows:
ngOnInit() {
// I do an ajax call here and populate more fields in the item.
this.getMoreData().subscribe(
updatedItem => this.item = updatedItem
);
}
The subscribe causes the async operation to execute and returns an observable. When the data comes back from the async operation, it executes the provided callback function and assigns the item to the returned item.
You declared item with #Input() decorator as:
#Input('item') public item: any;
But you use two-way binding on it:
<my-card [(item)]="item"></my-card>
If it is input only, it should be
<my-card [item]="item"></my-card>
Now if you invoke addSubItem() it should display the new added item.
this.item = this.getMoreData();
The getMoreData() doesn't make sense if you put it in your card component as you want to use the item passed via #Input()
Your component interactions are a little off. Check out the guide on the Angular docs (https://angular.io/guide/component-interaction). Specifically, using ngOnChanges (https://angular.io/guide/component-interaction#intercept-input-property-changes-with-ngonchanges) or use a service to subscribe and monitor changes between the parent and the child (https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service).

Javascript Angular 4 Change ngClass from another Component

I currently have this code in my app.component.ts
app.component.html
<div [ngClass]="myclass">
...rest of the content here
</div>
This I have the this:
<button (click)="changeClass('myFavClass')">Change Class to myFavClass</div>
app.component.ts
export class AppComponent {
myclass: string;
changeClass(myclass) {
this.myclass = myclass;
}
}
Now, all this works fine BUT I now want to put the triggering button on another component.
If I put this on another component:
<button (click)="changeClass('myFavClass')">Change Class to myFavClass</div>
How can I get it to change the class?
There are two ways you can do this you can use output with an EventEmit
Or you can set up a service that monitors the changes to a variable and use that as the control point for the change.
Personally, I use services for this instance as its easier to manage the code and its flow.
This answer has all the code in you need to look at.
Changing a value in two different components at the same time Angular 2
Hope that helps
There are at least two options. Subject and Observable or if this another component is a parent you can use #Input.
Subject and Observable method:
angular guide Highly recommended to read whole page.
Some component
export class SomeComponent {
constructor(private ClassService: ClassService) { }
private changeClass(class) {
this.ClassService.changeClass(class);
}
}
Another Component
export class AnotherComponent implements OnInit, OnDestroy {
constructor(private ClassService: ClassService) { }
private class: string = "";
private subscribtion: Subscribtion;
ngOnInit(): void {
this.Subscribtion = this.ClassService.someClass$.subscribe(
(class) => { this.class = class; }
)
}
ngOnDestroy(): void {
this.Subscribtion.unsubscribe();
}
}
Service
#Injectable();
export class ClassService{
constructor() { }
private someClassSource= new Subject<string>();
someClass$= this.someClassSource.asObservable();
changeClass(class) {
this.someClassSource.next(class);
}
}
taken from my answer
#Input method:
angular guide
This is very simple, when you click button changeClass method will change elClass which will be passed to another component by #Input decorator, every change of #Input will cause a detect changes which will detect that value has changed so class will change to myClass.
Parent component
parent.component.html
<another-component [elementClass]="elClass"></another-component>
<button (click)="changeClass('myClass')">change class<button>
parent.component.ts
export class ParentComponnet {
private elClass: string = "";
changeClass(class: string) {
elClass = class;
}
}
Another component (must be child component)
another.component.html
<div [ngClass]="elementClass">
another.component.ts
export class AnotherComponent {
#Input() elementClass: string;
}
There is also Child to Parent interaction via #Output (emitting event) angular guide

Categories

Resources