Using Observables to propagate changes in Angular 2 - javascript

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.

Related

How exactly works this Angular 5 example handling comunication between a parent and child component?

I am starting with Angular and I have some doubts about how exactly works this example related to comunication between a parent and a child component.
So I have this PARENT COMPONENT. This component is used to show a list of items (each item is represend by the child component). Interacting with this component I can add and remove items from this list of items.
This is the parent Component :
#Component({
selector: 'app-products',
templateUrl: './products.component.html'
})
export class ProductsComponent {
productName = 'A Book';
isDisabled = true;
products = ['A Book', 'A Tree'];
constructor() {
setTimeout(
() => {
this.isDisabled = false;
}, 3000)
}
onAddProduct() {
this.products.push(this.productName);
}
onRemoveProduct(productName: string) {
this.products = this.products.filter(p => p !== productName);
}
}
And this is its template :
<h1>My Products</h1>
<input *ngIf="!isDisabled" type="text" [(ngModel)]="productName">
<button *ngIf="!isDisabled" (click)="onAddProduct()">Add Product</button>
<app-product
(productClicked)="onRemoveProduct(product)"
*ngFor="let product of products"
[productName]="product">
</app-product>
Then I have the child component representing a single item of the list handled by the parent component. This is the child Component :
#Component({
selector: 'app-product',
templateUrl: './product.component.html',
styleUrls: ['./product.component.css']
})
export class ProductComponent {
#Input() productName: string;
#Output() productClicked = new EventEmitter();
onClicked() {
this.productClicked.emit();
}
}
And this is te children template :
<article class="product" (click)="onClicked()">
<div>{{ productName }}</div>
<p>An awesome product!</p>
</article>
Ok, now I have some doubts about how these 2 components interact together:
In the parent component template :
<app-product
(productClicked)="onRemoveProduct(product)"
*ngFor="let product of products"
[productName]="product">
</app-product>
that is referring to the child component (labeled by app-product).
It seems to me that basically I am iterating on the products list defined into the parent component class (an array of strings) and that each of these string is passed to the productName variable defined into the child component class, to do it I am using the #Input() decorator on this property in the child component class:
#Input() productName: string;
Basically this #Input() decorator hallow the parent component to "inject" the value into the child component property at each iteration.
Is it? or am I missing something?
Then I have the behavior that handle the item removal from the items list: when an element of the list is clicked (implemented by the child component). This element is removed from the list (and so from the page).
How it works (my idea):
Each element is represented by this code in the child component view:
<article class="product" (click)="onClicked()">
<div>{{ productName }}</div>
<p>An awesome product!</p>
</article>
When an object is clicked it is performed the onClicked() event into the child component class. I can't remove the selected object directly from the child component class because the array of items is defined into the parent component class. So this method emit an event:
onClicked() {
this.productClicked.emit();
}
having "type" productClicked (is it an event type or what?). This event is received in the parent component view:
(productClicked)="onRemoveProduct(product)"
that call the onRemoveProduct(product) method that remove the object having this name from the array.
Is it correct or in my reasoning am I missing something?
Another doubt is: is it a neat and correct way to handle events and this kind of situation?
You are right in both cases !
And according to the angular guide, this is a correct way to handle communication between parent/child components.

Angular bind ViewChild to Property in Class

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.

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

Shared value between Parent and Child manipulation

I have two components: Parent, Child.
Here is the scenario:
Child gets the value from Parent which continue changes
Child is always updated for the changed value
Child manipulates the value and displays it
On the Parent side the value doesn't get changed just becauseof it's manipulated on the Child side
An example use case:
I've tried this with the #Input. Because the #Input value gets manipulated on the Child side, on the Parent side it changes also. This is what I want to prevent, but also still want to keep getting the updated value from the Parent side.
An example code with #Input:
#Component({
selector: 'c-parent',
template: `
<div>{{question.name}}</div>
<button type="button" label="xxx" (click)="changeTheValue()"></button>
<br/>
<c-child [tmpQuestion]="question"></c-child>`
})
export class Parent implements OnInit {
question: Question; // don't get changed from the Child side
ngOnInit() {
question.name = "1";
}
changeTheValue(){
question.name = "2";
}
}
#Component({
selector: 'c-child',
template: `<div>{{tmpQuestion.name}}</div>`
})
export class Child implements OnInit {
#Input() tmpQuestion: Question; // be updated for the changes
ngOnInit() {
tmpQuestion.name = "This is the question: " + question.name; //manipulate the value
}
}
How can I do this with #Input approach or do I need to use something else?
Plunker
Using this
Added this to variables inside functions so question.name becomes this.question.name.
Primatives
Primatives (String, Number, Boolean, Symbol) are easier to work with if you want the child component to detect changes so in the parent component I sent the name property into the child component's input field.
Child Component Manipulated Value
A few things need to happen to display a manipulated value in the child component:
Create a variable for the manipulated value, I used manipulatedValue.
Move the manipulation logic into it's own function
like this:
manipulate() {
this.manipulatedValue = "This is the question: " + this.tmpQuestionName;
}
Call the manipulate function inside both ngOnInit and ngOnChanges
Pipes
If all you need is to do value manipulation you might be better off using a pipe
Parent Component
#Component({
selector: 'c-parent',
template: `
<div>Parent Question Name: {{question.name}}</div>
<button type="button" label="xxx" (click)="changeTheValue()">Change the value</button>
<br/>
<c-child [tmpQuestionName]="question.name"></c-child>`
})
export class Parent implements OnInit {
question = { name: '1' };
ngOnInit() {
this.question.name = "1";
}
changeTheValue(){
this.question.name = 'new ' + this.question.name;
}
}
Child Component
#Component({
selector: 'c-child',
template: `<div>Child Manipulated Value: {{manipulatedValue}}</div>`
})
export class Child implements OnChanges, OnInit {
#Input() tmpQuestionName; // be updated for the changes
manipulatedValue;
ngOnInit() {
this.manipulate();
}
ngOnChanges() {
this.manipulate();
}
manipulate() {
this.manipulatedValue = "This is the question: " + this.tmpQuestionName; //manipulate the value
}
}

Angular 2 Child Component removing Item from Parent Component Array

Currently having issue with Child component trying to remove Parent component's Array.
Parent Component:
#Component({
selector: 'parent',
templateUrl: 'app/templates/parent.html'
})
export class ParentComponent {
public items = [];
}
Parent HTML
<child [items]="items"></child>
<product *ngFor="let item of items><product>
Child component
#Component({
selector: 'child',
templateUrl: 'app/templates/child.html'
})
export class ChildComponent{
#Input() items;
emptyItems() {
this.items = [];
}
addItem() {
this.items.push({'title': 'foo'});
}
}
However when I call emptyItems/addItem function, the items array in the child view will reflect on changes, however on the parent component it doesnt change.
Do I need to use Output?
The right way is to use Output https://angular.io/docs/ts/latest/cookbook/component-communication.html#
However we can use two-ways binding on your items, this will reflect changes on both sides:
<child [(items)]="items"></child>
See more details https://angular.io/docs/ts/latest/guide/cheatsheet.html
Update:
Try to empty you array differently How do I empty an array in JavaScript?
Output should work https://angular.io/docs/ts/latest/api/core/index/Output-var.html
The idea is that you have to pass updated things from child to parent and manually update member items of the parent

Categories

Resources