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
Related
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.
hi i have this component . in this component i have #Input() userId: number[] = []; for send list of id to this component .
i use this component in other component , for example i use it in the news component :
<kt-user-post-list-select [userId]="noWriterIdList" (selectedUserId)="getSelectionUserList($event)">
</kt-user-post-list-select>
when i send a request to server for add news it return to me list of id : [1,2,3] and then i must send that ids to the kt-user-post-list-select with this [userId]="noWriterIdList" , But i have Problem : i need when pass the list to this component it track the changes and execute this function :
validateUSerIsWriter(ids: number[]): void {
for (let id = 0; id < ids.length; id++) {
let user = this.users.find(x => x.userId = id);
if (user != null) {
user.isDeleted = true;
}
}
}
but it dosent any work .
how can i solve this problem ???
There are 2 ways
Use ngOnChanges hook inside kt-user-post-list-select component. So you can listen for new Ids and execute the function validateUSerIsWriter. But remember, this comes with a cost of compromising performance.
Use Subject to subscribe for the newIds, and execute the function validateUSerIsWriter. In this case you don't need #Input decorator. Please refer this simple example https://stackblitz.com/edit/angular-subject-observable
I have two siblings in a parent, and data for a sibling is being updated, data is updated, but
ngOnInit(){}
not called.
https://stackblitz.com/edit/angular-5ctf2z?file=src/app/child2/child2.component.ts
Please help me,
Thanks in advance
Replace ngOnInit with ngOnChange as follows:
export class Child2Component implements OnChange {
#Input() data:any;
#Output() onEmit:EventEmitter<any> = new EventEmitter();
constructor() { }
ngOnChange() {
if(this.data.test1%2){
this.data.test2 += this.data.test1
}
}
}
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;
}
I have a public method that I exposed to window. This method talks to a Component and modifies a variable I am watching in my template. But when I change the value, the *ngIf() does not get triggered.
app.component
constructor(private _public: PublicService,) {
window.angular = {methods: this._public};
}
PublicService
export class PublicService {
constructor(
private _viewManager: ViewManagerComponent,
) {}
CallMe(){
this._viewManager.renderView('page1')
}
}
LayoutManagerComponent
#Component({
selector: 'view-manager',
template: `<page *ngIf="view == 'page1'"></page>`
})
export class ViewManagerComponent {
//This is the variable being watched
view = "page";
renderView = function(type){
console.log(type)
this.view = type;
console.log(this.view)
};
}
So the idea is that when the view initially loads, the view is blank. Then when I type angular.methods.CallMe() it modifies the view variable to page1 which should then show the html for the Component. If I console renderView function it is successfully getting called, just the view does not change.
----Update - Still not working -------
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(this.view)
this.zone.run(() => {
// type is 'page'
console.log(this.view)
this.view = type;
// type is 'page1'
console.log(this.view)
});
// type is 'page1'
console.log(this.view)
//cdRef errors:
//view-manager.component.ts:36 Uncaught TypeError: this.cdRef.detectChanges is not a function(…)
this.cdRef.detectChanges();
};
}
In this case Angular2 doesn't know that it needs to run change detection because the change is caused by code that runs outside Angulars zone.
Run change detection explicitely
contructor(private cdRef:ChangeDetectorRef) {}
someMethodCalledFromOutside() {
// code that changes properties in this component
this.cdRef.detectChanges();
}
Run the code that modifies the components properties inside Angulars zone explicitely
contructor(private zone:NgZone) {}
someMethodCalledFromOutside() {
this.zone.run(() => {
// code that changes properties in this component
});
}
The zone method is a better fit when // code that changes properties in this component not only changes properties of the current component, but also causes changes to other components (like this.router.navigate(), call method references of methods of other components) because zone.run() executes the code inside Angulars zone, and you don't need to explicitely take care of change detection in every component where a change might happen because of this call.
If you use function(...) instead of () => it's likely you'll get unexpected behavior with this in code inside the Angular component.
See also my answer to this similar question for more details Angular 2 - communication of typescript functions with external js libraries
update
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
self = this;
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(self.view)
self.zone.run(() => {
// type is 'page'
console.log(self.view)
self.view = type;
// type is 'page1'
console.log(self.view)
});
// type is 'page1'
console.log(self.view)
self.cdRef.detectChanges();
};
}