Angular 2 Child Component removing Item from Parent Component Array - javascript

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

Related

Re-render the particular component based on detected changes

I have 2 simple components. One is parent the other is child. The parent component has an Array, For each element in the Array, It renders the child component.
parent.component.ts
export class parent implements OnInit {
data: CustomType[] = [
{
id: "child1",
records: [] // array of string
},
{
id: "child2",
records: [] // array of string
}
];
ngOnInit() {}
}
parent.component.html
<section>
<ChildComponent *ngFor="let child of data | async" [obj]="child"/>
</section>
child.component.ts
export class child implements OnInit {
// The data is passed from the parent component
#Input() obj: CustomType;
ngOnInit() {}
}
child.component.html
<div>
{{ obj.id }}
</div>
The Problem
The current code works just fine. But the issue is if the records of an element change in the array, It re-renders all the children components. I want to re-render the exact component only.
I am wondering how to use the onPush Change Detection here.
For Example:
If data[0].records changes, It should re-render the data[0]'s child component only.
add the trackBy function so that it does not render everthing but only renders the one where the trackBy function is changed!
html file
<section>
<ChildComponent *ngFor="let child of data | async; trackBy: trackBy" [obj]="child"/>
</section>
ts file
trackBy(index, item) {
return item.id;
}
reference here

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.

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
}
}

Using Observables to propagate changes in Angular 2

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.

Update the data in one component based on what is clicked in another component in Angular 2

I have two components let's call them CompA and CompB. I would like for the clicked item object in CompA to appear in CompB. Here is what I have done so far.
CompA:
import {Component} from 'angular2/core';
import {CompB} from './compB';
#Component({
selector: 'comp-a',
template: '<ul>
<li *ngFor="#item of items" (click)="show(item)">
{{item.name}}
</li>
</ul>',
providers: [CompB]
})
export class CompA {
constructor(public _compB: CompB){}
show(item){
this._compB.displayItem(item);
}
}
CompB:
import {Component} from 'angular2/core';
#Component({
selector: 'comp-b',
template: '<div>
{{item.name}}
</div>'
})
export class CompB {
public newItem: Object;
constructor(){
this.newItem = {name: "TEST"};
}
displayItem(item){
this.newItem = item;
}
}
The problem is that when I click an item it doesn't change anything in CompB. I did a console log on CompB and I am getting the item object just fine but I view doesn't update with the clicked item's name. It just stays as 'TEST'.
Even if I set this.newItem in the displayItem function to a hardcoded string, it still doesn't change.
Update:
Both components are siblings in a main.html like this...
main.html
<div class="row">
<div class="col-sm-3">
<comp-a></comp-a>
</div>
<div class="col-sm-9">
<comp-b></comp-b>
</div>
</div>
Thats because the Component B you got injected in the constructor is not the component B used in the application. Its another component B that the hierarchical injector created, when Component B was added to the list of providers.
One way to do it is to create a separate injectable service, and inject it in both components. One component subscribes to the service and the other triggers a modification. For example:
#Injectable()
export class ItemsService {
private items = new Subject();
newItem(item) {
this.subject.next(item);
}
}
This needs to be configured in the bootstrap of the Angular 2 app:
boostrap(YourRootComponent, [ItemsService, ... other injectables]);
And then inject it on both components. Component A sends new items:
export class CompA {
constructor(private itemsService: ItemsService){}
show(item){
this.itemsService.newItem(item);
}
}
And component B subscribes to new items:
export class CompB {
constructor(itemsService: ItemsService){
itemsService.items.subscribe((newItem) => {
//receive new item here
});
}
Have a look at the async pipe, as its useful to consume observables in the template directly.
If you get a CompB instance passed to
constructor(public _compB: CompB){}
it's not the instance you expect but a different (new) one.
There are different strategies to communicate between components. This depends on how they are related in the view. Are they siblings, parent and child or something else. Your question doesn't provide this information.
For parent and child you can use data binding with inputs and outputs.
For siblings you can use data binding if you include the common parent (use it as mediator)
You always can use a shared service.
For data-binding details see https://angular.io/docs/ts/latest/guide/template-syntax.html

Categories

Resources