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
}
}
Related
I have a component that has a dynamic view, which I am implementing using an ng-template.
The following is that component, lets say parent.component.ts.
#Component({
...
template: '<div><ng-template child-area></ng-template></div>',
...
})
export class ParentComponent implements OnInit {
#ViewChild(ChildAreaDirective) childArea: ChildAreaDirective;
loadComponent (details) {
let _vcf = this.childArea.viewContainerRef;
let _cf= resolveComponentFactory(childrenList[details.name]);
// How do I pass the options object from here into the child component??
this.childOptions = details.options;
let componentRef = _vcf.createComponent(_cf);
}
}
The child.component.ts is written like this:
#Component ({
template: '<div>{{ title }}</div>'
})
I want to call the loadComponent method like this:
parent.loadComponent({ name: 'choose-doc', options: {title: "Main File"} });
How can I pass the options object into the child component, so that the title gets the value 'Main File'?
componentRef has the attribute instance which is the actual instance of the created component. From this instance, you can access to all public attributes and functions of the component.
You can map your options inside a function on the component or just set them from loadComponent.
Check the class doc: https://angular.io/api/core/ComponentRef#instance
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.
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
I'm using angular 2. I have a component with an input.
I want to be able to write some code when the input value changes.
The binding is working, and if the data is changed (from outside the component) I can see that there is change in the dom.
#Component({
selector: 'test'
})
#View({
template: `
<div>data.somevalue={{data.somevalue}}</div>`
})
export class MyComponent {
_data: Data;
#Input()
set data(value: Data) {
this.data = value;
}
get data() {
return this._data;
}
constructor() {
}
dataChagedListener(param) {
// listen to changes of _data object and do something...
}
}
You could use the lifecycle hook ngOnChanges:
export class MyComponent {
_data: Data;
#Input()
set data(value: Data) {
this.data = value;
}
get data() {
return this._data;
}
constructor() {
}
ngOnChanges([propName: string]: SimpleChange) {
// listen to changes of _data object and do something...
}
}
This hook is triggered when:
if any bindings have changed
See these links for more details:
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
https://angular.io/docs/ts/latest/api/core/OnChanges-interface.html
As mentioned in the comments of Thierry Templier's answer, ngOnChanges lifecycle hook can only detect changes to primitives. I found that by using ngDoCheck instead, you are able to check the state of the object manually to determine if the object's members have changed:
A full Plunker can be found here. But here's the important part:
import { Component, Input } from '#angular/core';
#Component({
selector: 'listener',
template: `
<div style="background-color:#f2f2f2">
<h3>Listener</h3>
<p>{{primitive}}</p>
<p>{{objectOne.foo}}</p>
<p>{{objectTwo.foo.bar}}</p>
<ul>
<li *ngFor="let item of log">{{item}}</li>
</ul>
</div>
`
})
export class ListenerComponent {
#Input() protected primitive;
#Input() protected objectOne;
#Input() protected objectTwo;
protected currentPrimitive;
protected currentObjectOne;
protected currentObjectTwo;
protected log = ['Started'];
ngOnInit() {
this.getCurrentObjectState();
}
getCurrentObjectState() {
this.currentPrimitive = this.primitive;
this.currentObjectOne = _.clone(this.objectOne);
this.currentObjectTwoJSON = JSON.stringify(this.objectTwo);
}
ngOnChanges() {
this.log.push('OnChages Fired.')
}
ngDoCheck() {
this.log.push('DoCheck Fired.');
if (!_.isEqual(this.currentPrimitive, this.primitive)){
this.log.push('A change in Primitive\'s state has occurred:');
this.log.push('Primitive\'s new value:' + this.primitive);
}
if(!_.isEqual(this.currentObjectOne, this.objectOne)){
this.log.push('A change in objectOne\'s state has occurred:');
this.log.push('objectOne.foo\'s new value:' + this.objectOne.foo);
}
if(this.currentObjectTwoJSON != JSON.stringify(this.objectTwo)){
this.log.push('A change in objectTwo\'s state has occurred:');
this.log.push('objectTwo.foo.bar\'s new value:' + this.objectTwo.foo.bar);
}
if(!_.isEqual(this.currentPrimitive, this.primitive) || !_.isEqual(this.currentObjectOne, this.objectOne) || this.currentObjectTwoJSON != JSON.stringify(this.objectTwo)) {
this.getCurrentObjectState();
}
}
It should be noted that the Angular documentation provides this caution about using ngDoCheck:
While the ngDoCheck hook can detect when the hero's name has changed,
it has a frightful cost. This hook is called with enormous frequency —
after every change detection cycle no matter where the change
occurred. It's called over twenty times in this example before the
user can do anything.
Most of these initial checks are triggered by Angular's first
rendering of unrelated data elsewhere on the page. Mere mousing into
another input box triggers a call. Relatively few calls reveal actual
changes to pertinent data. Clearly our implementation must be very
lightweight or the user experience will suffer.