I have struggling to make it work in Angular. I have a host component (parent) which is using a child one to render a dropdown list. The source of the list is been passed from the parent. So for example, if the parent pass 5 items on the source property, the child component will render 5 options for the dropdown list.
this is part the code where I call the child component:
parent.component.html
<ng-container>
<th mat-header-cell *matHeaderCellDef>
<app-column-header
[id]="column.id"
[name]="column.name"
[source]="myObject.options"
></app-column-header>
</th>
</ng-container>
parent.component.ts
export class ParentComponent {
#ViewChild(ChildComponent) ChildComponent;
// more code
private updateChildSource() {
this.child.updateDataSource(myObject.options);
}
}
This is working OK so far.
NOW, the challenges I am having is that the list of items to be passed needs to be dynamic (myObject.options). So, for example, the first time lets says I am passing 5 items. Angular takes those 5 items and render the child component properly. However, once the child component is already rendered and if I changes the source to be 2 items instead of 5 from the parent and pass the new source, the child component is not rendering the new items (2).
child.component.ts
export class ColumnHeaderComponent implements OnInit, OnChanges {
#Input() id: string;
#Input() name: string;
#Input() source: any[];
childField: any;
ngOnInit(): void {
const options = this.doStuffHere(this.source);
this.childField= {
id: this.id,
options,
};
}
updateDataSource(newSource: Option[]): void {
console.log(`print call from parent. old options:
${JSON.stringify(this.childField.options)} - new options: ${JSON.stringify(newSource)}`);
this.source= newSource;
const options = this.doStuffHere(this.source);
this.childField= {
id: id,
options,
};
}
ngOnChanges(changes: SimpleChanges) {
console.log('changed');
for (const propName in changes) {
const chng = changes[propName];
const cur = JSON.stringify(chng.currentValue);
const prev = JSON.stringify(chng.previousValue);
console.log(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
}
As mentioned before, the child component is receiving the original and new items, even the ngOnChanges method is capturing it and printing the values properly. But for some reason I don't know yet the child component is still rendering the old items (5) instead of the new ones (2).
Not sure, if I am missing something here? Or the question is clear enough to illustrated the problem I am facing.
Could you point me to the correct direction how to solve this? Thanks in advance.
As said Marek you can directly pass the list from your parent component as the input of your child component. The [list]="list" notation is already reactive.
Then you'll just have to use the list in the drop-down in your child component.
Note : Not useful here, but as #Input you can set a function instead of a variable. It will be triggered every time the input value change.
Related
In a nutshell, I have a component which works as a text input. The parent component passes data into this component using #Input bindings. When the change event gets fired via the #Output binding, I perform some automatic validation in the parent component. This is to remove erroneous values and replace them with some sensible default for a better user experience.
Here is a basic version of the parent component
#Component({
selector: 'parent',
template: `
<div>
<child [value]="item.key1"
(valueChange)="onValueChange($event)">
</child>
</div>
`
})
export class Parent {
item = {
key1: 'test value',
key2: 'another test value'
};
onValueChange(newValue) {
// Perform some validation and set default
if (newValue.length > 4) {
newValue = newValue.substring(0, 4);
}
this.item.key1 = newValue;
}
}
And the child component
#Component({
selector: 'child',
template: `
<div>
<input type="text"
[(ngModel)]="value"
(blur)="onBlur($event)" />
</div>
`
})
export class Child {
#Input() value: string;
#Output() valueChange = new EventEmitter();
onBlur() {
this.valueChange.emit(this.value);
}
}
See here for a Plunker example
The issue I am having is as follows:
When entering a value in the child input and firing the blur event, the new value is bubbled up to the parent and the validation is applied - if the validation causes the value to get modified, it bubbles back down to the child's value correctly - happy days.
However, if the first 4 characters stay the same, and you append additional characters, upon blur the validation will still be applied and the parent's value will get updated correctly, but the child will preserve the "invalid" value - no more happy days.
So it looks to me like Angular isn't detecting the change in the parents data (fair enough because it hasn't technically changed) so it isn't sending the "latest" value back down to the child.
My question is, how can I get the child text input to always show the correct value from the parent, even if it technically hasn't "changed"?
better solution
#Jota.Toledo's good comment made me realise that my approach, although it did serve as a quick workaround for me at the time, it's not a good one, so I actually went and made some changes to my project that can work for you as well, also following his suggestion of
Delegating that "validation" logic into the child component
while keeping the validation definition in the parent as a function that's passed to the child as an #Input param.
This way I'd give the parent 2 public vars
a object (item)
a function (validation)
and change onValueChange function to only update the item.key1 as it will be already validated.
In the child add a new #Input param (validation) of type Function and use that function to validate the newValue inside the onBlur, before emiting the value to the parent.
I have the feeling that what I've written here might "sound" a bit confusing so I'm adding the code for what I'm trying to explain.
Parent
#Component({
selector: 'parent',
template: `
<div>
<p><b>This is the parent component</b></p>
<child [value]="item.key1"
[validation]="validation"
(valueChange)="onValueChange($event)">
</child>
<p><b>Variable Value:</b> {{item | json}} </p>
</div>
`
})
export class Parent {
item = {
key1: 'test value',
key2: 'another test value'
};
validation = (newValue) => {
if (newValue.length > 4) {
newValue = newValue.substring(0, 4);
}
return newValue;
}
onValueChange(newValue) {
this.item.key1 = newValue;
}
}
Child (leaving the template part out because it's unchanged)
export class Child {
#Input() value: string;
#Input() validation: Function;
#Output() valueChange = new EventEmitter();
onBlur() {
this.value = this.validation(this.value)
this.valueChange.emit(this.value);
}
}
previous answer (not a good approach)
I had a similar problem in the past and the way I solved was to clear my var and then giving it the final value inside a setTimeout without specifying the milliseconds.
in your parent component it would look something like this:
onValueChange(newValue) {
console.log('Initial: ' + newValue);
if (newValue.length > 4) {
newValue = newValue.substring(0, 4);
}
console.log('Final: ' + newValue);
this.item.key1 = '';
setTimeout(() => {
this.item.key1 = newValue;
});
}
However, if the first 4 characters stay the same, and you append
additional characters, upon blur the validation will still be applied
and the parent's value will get updated correctly, but the child will
preserve the "invalid" value - no more happy days.
So it looks to me like Angular isn't detecting the change in the
parents data (fair enough because it hasn't technically changed) so it
isn't sending the "latest" value back down to the child.
Correct. The fact is that angular is "caching" the latest value passed through the value input property, and as you recognize, you arent really pushing new values into it if you dont change the first 4 chars.
You can check this by adding ngOnChanges(changes) to your child and logging the changes value into console; in the above case nothing is logged, as no new value for value is pushed through.
You could overcome this by:
Enforcing that a new value is always pushed through the input property by wrapping the value in an object and unwrapping it in the child component. (Bad approach IMO)
Delegating that "validation" logic into the child component, so that only "validated" values are emitted through the output property.
You should create a Subject on the ParentComponent :
export class ParentComponent {
parentSubject:Subject<Item> = new Subject();
notifyChildren() {
this.parentSubject.next('new item value');
}
}
Pass it as Input() on the ChildComponent :
<child [parentSubject]="parentSubject"></child>
And finally subscribe to it on the ChildComponent :
export class ChildComponent {
#Input() parentSubject:Subject<Item>;
ngOnInit() {
this.parentSubject.subscribe(event => {
//do your stuff with the updated value
});
}
you can update your code for onValueChange method in parent.ts with following
onValueChange(newValue) {
this.item.key1 = null;
setTimeout(() => {
console.log('Initial: ' + newValue);
if (newValue.length > 4) {
newValue = newValue.substring(0, 4);
}
console.log('Final: ' + newValue);
this.item.key1 = newValue;
})
}
I'm using mobx-react / mobx-react-lite for state management
Using classes i can define a non observable idToDelete to store the clicked item id, open a Modal using an observable and when the user clicks "Delete", i know the item to delete. The id is "remembered" by the component trough the re-renders
class Greeting extends React.Component {
idToDelete = null;
confirmDelete = id => {
this.idToDelete = id;
openConfirm = true;
}
closeModal = () => {
openConfirm = true;
this.idToDelete = null;
}
#observable
openConfirm = false;
render() {
// List of items with delete button
<button onClick=this.confirmDelete(id)>Delete</button>
// Confirm Delete Modal
}
}
But in a stateless component the id will become null (the initialization value) on each re-render.
Using useLocalStore hook i can store observable values:
All properties of the returned object will be made observable
automatically
But i dont want to re-render just because i am storing/changing the id.
Using React.React.createContext / useContext seems like a bit overkill to me (it's kind of private value and it is not relevant outside the component itself)
Is there a "local storage" way to achieve this? (without observable convertion)
What are best practices for this situation?
You can use the useRef hook to save the value. A change to this value will not trigger a re-render and the value will remain the same across renders unless you override it.
Its also explained in detail here
Yes! The useRef() Hook isn’t just for DOM refs. The “ref” object is a
generic container whose current property is mutable and can hold any
value, similar to an instance property on a class.
eg:
import { useRef } from 'react';
const idToDelete = useRef("");
confirmDelete = id => {
idToDelete.current = id;
}
closeModal = () => {
idToDelete.current = null;
}
Also mind the catch, you need to use .current to access the data.
It seems there is no way to watch changes in the parent component when using two-way data binding.
I have a custom input component for collecting a tag list. Two-way data binding is setup and working between this component and its parent.
// the parent component is just a form
// here is how I'm adding the child component
<input-tags formControlName="skillField" [(tags)]='skillTags' (ngModelChange)="skillTagUpdate($event)">
</input-tags>
In the parent component how do you watch the bound variable for changes? While it's always up to date (I've confirmed this) I cannot find any guidance on reacting to changes.
I've tried:
ngOnChanges(changes: SimpleChanges) {
if (changes['skillTags']) {
console.log(this.skillTags); // nothing
}
}
And
skillTagUpdate(event){
console.log(event); // nothing
}
UPDATE:
TWDB IMHO is not what it is advertised to be. Whenever I arrive at this place where TWDB seems to be a solution I rearchitect for a service and or observable communication instead.
When you implement a two way binding of your own, you have to implement an event Emitter. The syntax for that is mandatory.
this means that you have a hook to listen to if the value changes.
Here is a demo :
<hello [(name)]="name" (nameChange)="doSomething()"></hello>
_name: string;
#Output() nameChange = new EventEmitter();
set name(val) {
this._name = val;
this.nameChange.emit(this._name);
}
#Input()
get name() {
return this._name;
}
counter = 0;
ngOnInit() {
setInterval(() => {
this.name = this.name + ', ' + this.counter++;
}, 1000);
}
Stackblitz
From what I know, this seems the less annoying way to use it, and any two way binding will follow the same rule no matter what, i.e. it ends with the Change word !
Your implementation is actually not two-way databinding, the parent and child component are just sharing a reference on the same skillTags variable.
The syntax [(tags)]='skillTags' is syntaxic sugar for [tags]='skillTags' (tagsChange)='skillTags = $event'
You need to implement tagsChange in the child component like this: #Output('tagsChange') tagsChange = new EventEmitter<any>();, then any time you want to modify tags into the children component, dont do it directly, but use this.tagsChange.emit(newValue) instead.
At this point, you'll have real two-way databinding and the parent component is the unique owner of the variable (responsible for applying changes on it and broadcasting changes to the children).
Now in your parent component, if you want to do more than skillTags = $event (implicitly done with [(tags)]='skillTags'), then just add another listener with (tagsChange)='someFunction($event)'.
StackBlitz Demo
Don't know if this is what you're looking for, but have you tried using #Input()?
In child component
#Input() set variableName(value: valueType) {
console.log(value);
}
In parent component
<input-tags formControlName="skillField" [(tags)]='skillTags'
[variableName]="skillTagUpdate($event)"></input-tags>
The input function is called every time the object binded to the function is changed.
you could listen to the change:
<input-tags formControlName="skillField" [tags]='skillTags' (tagsChange)='skillTags=$event; skillTagUpdate();'></input-tags>
or use getter and setter:
get skillTags(): string {
return ...
}
set skillTags(value) {
variable = value;
}
another approach:
export class Test implements DoCheck {
differ: KeyValueDiffer<string, any>;
public skillTags: string[] = [];
ngDoCheck() {
const change = this.differ.diff(this.skillTags);
if (change) {
change.forEachChangedItem(item => {
doSomething();
});
}
}
constructor(private differs: KeyValueDiffers) {
this.differ = this.differs.find({}).create();
}
}}
1.you can use output(eventemitter)
2.easiest solution is rxjs/subject. it can be observer and observable in same time
Usage:
1.Create Subject Property in service:
import { Subject } from 'rxjs';
export class AuthService {
loginAccures: Subject<boolean> = new Subject<boolean>();
}
2.When event happend in child page/component use :
logout(){
this.authService.loginAccures.next(false);
}
3.And subscribe to subject in parent page/component:
constructor(private authService: AuthService) {
this.authService.loginAccures.subscribe((isLoggedIn: boolean) => {this.isLoggedIn = isLoggedIn;})
}
Update
for two-way binding you can use viewchild to access to your child component items and properties
<input-tags #test></<input-tags>
and in ts file
#ViewChild('test') inputTagsComponent : InputTagsComponent;
save()
{
var childModel = this.inputTagsComponent.Model;
}
What I'm trying to do ?
I have a parent component(say parent) which contains 2 children components(say child-1 and child-2). When one of the DOM element value within child 1 changes, I'm assigning the new value to one of the properties of an object and emitting the object to parent. Parent then assigns the incoming object to its own local object, which I'm passing as an input([setupValues]) to child 2 component. Within Child 2, when there is any change to that #Input object, I want to assign one of its properties to a local variable(which is not happening because the child 2 Onchanges doesn't get fired even when one of the properties within #Input object has changed).
My Source Code(most removed due to brevity)
Consider I have an interface ISetups.ts
export interface ISetups {
DOB: string;
DOD: string;
DOE: string;
}
Markup of parent.html goes as follows
<child-1 [input]="someVariable" (setupsChanged)="onSetupsChange($event)">
</child-1>
{{ allSetups.DOB }} <!-- the change is shown correctly here -->
<child-2 [setupValues]="allSetups"></child-2>
In parent.ts
export class ParentComponent {
someVariable: string;
allSetups: ISetups;
onSetupsChange(allSetups: ISetups) {
// emitted object from child 1 is assigned to local object
this.allSetups = allSetups;
}
}
In child-1.html
<input class="form-control" [(ngModel)]="ownDOB" (blur)="emitChange()" >
In child-1.ts
export class Child1Component {
#Input() input: string;
#Output() setupsChanged: EventEmitter<ISetups> = new EventEmitter<ISetups>();
allSetups: ISetups;
ownDOB: string;
emitChange() {
this.allSetups.DOB = this.ownDOB;
this.setupsChanged.emit(this.allSetups); //emitting object to parent
}
}
In child-2.html
<p>{{ localDOB }}</p>
In child-2.ts
export class Child2Component implements OnChanges {
localDOB: string;
#Input() setupValues: ISetups;
ngOnChanges(): void {
console.log("this does not fire if setupValues.DOB changes");
this.localDOB = this.setupValues.DOB;
}
}
What worked ?
Passing the property directly as another input worked
<child-2 [DOB]="allSetups.DOB" [setupValues]="allSetups"></child-2>
The above works but obviously its not a nice way & I don't want to do this.
The above proves that when I send the variable on its own, it fires the OnChanges but doesn't fire when just one of the properties within an object is changed.
Any help is much appreciated.
this is happened because your reference to object is not changed, so if in your parent.ts change your code to this, it should work. By this way (using spread operator) you are creating a new instance of object
onSetupsChange(allSetups: ISetups) {
// emitted object from child 1 is assigned to local object
this.allSetups = {...allSetups};
}
iam trying to make a simple grid component and i have a trouble with updating view after emitting event !
Please explain who knows, why after updating simple component, view do not re-rendered ? whats wrong with this code ?
export class GridComponent implements OnInit {
#Input() resource: any [];
#Output() saveModel = new EventEmitter();
#Output() deleteModel = new EventEmitter();
attributes: any[];
isUpdating: boolean = false;
updatingID: number;
constructor() {}
ngOnInit() {
this.attributes = Object.keys(this.resource[0]);
}
toggleUpdate(id, flag = true) {
this.isUpdating = !this.isUpdating;
this.updatingID = flag ? id : undefined;
}
destroy(id) {
this.deleteModel.emit(id);
}
save(model) {
this.saveModel.emit(model);
this.toggleUpdate(model.id, false);
}
cancel(id) {
this.toggleUpdate(id, false);
}
}
Full example here https://plnkr.co/edit/InxsHu9GwCtMplYoocsS?p=preview
The resource data is updated properly in parent and child components, just the form doesn't show the update.
I think you need to change the values pipe to only return the keys but not the values and then access the values using the *ngFor variables with the keys to get the values in the view directly.
EDITED:
Günter Zöchbauer, thank you saved my time !
I think you need to change the values pipe to only return the keys but not the values and then access the values using the *ngFor variables with the keys to get the values in the view directly.
this was root of evil