I'm trying to figure out how to bind a view child to a child component of a class inside of my view.
I have a models that emulates binary expression:
export interface IODataExpression{
}
export class ODataExpressionDescriptor implements IODataExpression{
property: ODataProperty;
selectedFunction: ODataFunctionDescriptor;
value: any;
isNegated: boolean = false;
}
export class ODataBinaryExpressionDescriptor implements IODataExpression{
left: IODataExpression;
right: IODataExpression;
operator: ODataBinaryOperators;
}
I have a component class which looks like so:
binaryExpression: ODataBinaryExpressionDescriptor = new ODataBinaryExpressionDescriptor();
binaryOperatorKeys: any;
binaryOperators = ODataBinaryOperators;
#ViewChild('left') leftExpression: OdataSimpleFilterComponent;
the left property points to a component which internally has a property:
odataExpression: ODataExpressionDescriptor = new ODataExpressionDescriptor();
How can I make it so that the binaryExpression.left always equals the view childs leftExpression.odataExpression?
Use an EventEmitter.
In OdataSimpleFilterComponent
#Output() odataExpressionChange = new EventEmitter<ODataExpressionDescriptor>();
Then, whenever tha value changes internally in the component, you do:
this.odataExpressionChange.emit(this.odataExpression);
In the main component, you'll have to do in ngAfterViewInit (or ngAfterViewChecked), to make sure that leftExpression is initialised:
ngAfterViewInit() {
leftExpression.odataExpressionChange.subscribe(data => {
this.binaryExpression.left = data;
}
}
This way, whenever the value changes in the child component, you'll receive a notification (via the subsrcription to the EventEmitter) and can react accordingly.
Of course some details might change, as I can't know all of the details of your implementation.
Related
I have a component profile.component.ts in which there's a variable, this.candidate, which subscribes to and gets updated like this
this.candidateSub = this.store.select(selectCandidateState).subscribe((candidateState) => {
this.candidate = candidateState;
})
This component acts like a base component and there's a child component that extends this component,
export class SkillsComponent extends profile implements OnInit, OnDestroy
Now I want to have another variable in this child component that updates itself whenever this.candidate gets updated in the base component. Something like this:
this.candidateSkills = this.candidate.skills
The problem is I can't get it done simply by declaring this.candidateSkills = this.candidate.skills in ngOnit as it doesn't watch and listen to changes and update itself when this.candidate gets updated in base component.
How should I do it?
In parent component:
this.storeObservable = this.store.select(selectCandidateState)
this.candidateSub = this.storeObservable.subscribe((candidateState) => {
this.candidate = candidateState;
})
In child component:
this.storeObservable.subscribe((candidateState) => {
// do something
})
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.
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).
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
I have two components, a Parent and a Child:
// Parent Directive
#Component({
selector: 'parent-directive',
template: `
<button (click)="nextVal($event)"></button>
<button (click)="prevVal($event)"></button>
<child-directive [content]="myValue"></child-directive>`,
directives: [ChildDirective]
})
export class ParentDirective {
public myValue : string;
constructor() {this.myValue = "Hello";}
nextVal() {this.myValue = "Next";}
prevVal() {this.myValue = "Prev";}
}
This is the child directive:
// Child directive
type ObservableContent = Observable<string>;
#Component({
selector: 'child-directive',
template: `<div></div>`
})
export class ChildDirective {
#Input() content : ObservableContent;
subscription : Subscription;
constructor() {
// I instantiate the content property as an observer. I want to see if it logs anything.
this.content = new Observable<string>(ob => {console.log('constructor', ob)});
// I'm trying to get the propagated values here.
this.subscription = this.content.subscribe(value => { console.log('value', value);});
}
}
Let me break down what I'm trying to do here. I have a child component nested in parent component. The parent has two buttons, next and prev, which when clicked change a property bound to the scope of the parent.
The child has another property, content that is bound to the myValue scope property of the parent. When I update myValue in the parent, I want the content property of the the child to change. However when I try to subscribe to that value the subscription listener is never called. What am I doing wrong?
As I can see content is a string and not an Observable. So you don't need to use .subscribe here as it will throw an error.
In your child component this.content will always give you the latest value. Just use changeDetection: ChangeDetectionStrategy.OnPush. This makes sure that angular updates the component only if one of its input attributes is changed.
To get the latest value of content in component use the ngOnChanges lifecycle method provided by angular.
// Child directive
type ObservableContent = Observable<string>;
#Component({
selector: 'child-directive',
template: `<div>{{content}}</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildDirective {
#Input() content : ObservableContent;
ngOnChanges(changes) {
console.log('new content' + changes.content.currentValue);
console.log('old content' + changes.content.previousValue);
}
}
The content in template will always reflect the updated value due to Angular's change detection.