Angular sibling ngInit method not call after data update - javascript

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

Related

Update child component from parent in Angular

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.

Angular Two-Way Data Binding and Watching for Changes in Parent Component

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

how to show value of another component in this exact code Angular 2 and higher [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
Good day, I would like to ask you to review this code and help me figure out where is mistake that variable itemCount doesn´t show any value in about.component.html but in home.component.html all work fine. I only want to show itemCount in about.component.html so I tried to use EventEmittent method. Here is code.
Home.component.ts
export class HomeComponent implements OnInit {
itemCount : number = 0; ---here variable in home component ok
btnText: string = 'Add an item';
goalText: string = 'My first life goal';
goals = [];
#Output()sharedItemCount = new EventEmitter<number>(); -- own event
created
share(){ ---method the invokes own event
this.sharedItemCount.emit(this.itemCount);
}
constructor(private _data: DataService) { }
ngOnInit() {
this._data.goal.subscribe(res =>this.goals = res);
this.itemCount = this.goals.length;
this._data.changeGoal(this.goals);
this.share(); --running method to invoke changes with itemCount variable
}
addItem(){
this.goals.push(this.goalText);
this.goalText = '';
this.itemCount = this.goals.length;
this._data.changeGoal(this.goals);
this.share();--running method to invoke changes with itemCount variable
}
removeItem(i){
this.goals.splice(i,1);
this.itemCount = this.goals.length;
this._data.changeGoal(this.goals);
this.share();--running method to invoke changes with itemCount variable
}
Here is implementation from About.component.html where I want to see value of itemCount.
<p (sharedItemCount) = "onShareItemCount($event)">({‌{itemCount}})</p> --receiving own event that invokes onShareItemCount method and passing them value itemCount and also them showing variable itemCount in brackets
And here is implementation from About.component.ts
export class AboutComponent implements OnInit {
goals: any;
itemCount : number; --variable
constructor(private route: ActivatedRoute, private router: Router, private
_data: DataService) {
this.route.params.subscribe(res => console.log(res.id));
}
ngOnInit() {
this._data.goal.subscribe(res =>this.goals = res);
}
onShareItemCount(itemCount: number){--method that set up localvariable the
same as passed through event
this.itemCount = itemCount;
}
The #Output event emitter sharedItemCount in your HomeComponent is useless, you don't need it because you're already using a shared service (_data: DataService) in both components.
Change your About.component.html file to:
<p>({‌{itemCount}})</p>
And in About.component.ts file, change ngOnInit function to:
ngOnInit() {
this._data.goal.subscribe(res => {
this.goals = res;
this.itemCount = res.length;
});
}
Your ngOnInit function gets updated on each change of the goals object using rxjs Observable, resulting on updating your template. Just don't forget to unsubscribe from it on ngOnDestroy.

Execute function after DOM has finished rendering

I recall reading the excerpt below from a blog.
$timeout adds a new event to the browser event queue (the rendering engine is already in this queue) so it will complete the execution before the new timeout event.
I'm wondering if there is a better way in angular/ javascript than using
setTimeout(() => {
// do something after dom finishes rendering
}, 0);
to execute code when the DOM has completely finished a task such as updating an *ngFor and rendering the results on the page.
You might try the ngAfterViewInit life-cycle hook, which is the chronologically last single-fire life-cycle hook.
https://angular.io/guide/lifecycle-hooks
It works much like ngInit but it fires after the view and child views have initialized.
If you need something that fires every time the DOM finishes you can try ngAfterViewChecked or ngAfterContentChecked.
problem:
I need to run a function sometimes after some parts loaded. (I wanted to stretch out an input and a label)
ngAfterViewInit and route change detection didn't solve my problem
Solution:
I made a component which
import { Component, AfterViewInit } from '#angular/core';
declare var jquery: any;
declare var $: any;
#Component({
selector: 'app-inline-label',
templateUrl: './inline-label.component.html',
styleUrls: ['./inline-label.component.scss']
})
/** InlineLabel component*/
/**
this component stretch inline labels and its input size
*/
export class InlineLabelComponent implements AfterViewInit {
/** InlineLabel ctor */
constructor() {
}
ngAfterViewInit(): void {
var lblWidth = $('.label-inline').width();
var parentWidth = $('.label-inline').parent().width();
var fieldWidth = parentWidth - lblWidth;
$('.form-control-inline').css("width", fieldWidth);
}
}
then I used it anywhere in my html like
<app-inline-label></app-inline-label>
even if my html had *ngIf="", I used app-inline-label inside that tag and solved all my problems
Actually it will be fired exactly when <app-inline-label> </app-inline-label> being rendered
If the function to be rendered multiple times ngAfterContentChecked will be preferable.
app.component.ts
export class AppComponent implements OnInit, OnDestroy {
searchRegister: any = [];
constructor() {
}
ngAfterContentChecked(): void {
this.setHTMLElements();
}
setHTMLElements() {
this.searchRegister = ['cards-descriptor__subtitle','insights-card__title','article-headline__title','wysiwyg__content','footer-logo__heading','hero-breadcrumbs__blurb','multi-column-text__body','small-two-column-image-text__blurb','two-column-image-text__blurb','image-blurbs-expandable__desc',];
for (var val of this.searchRegister) {
var classLength = this.dom.body.getElementsByClassName(val).length;
for (var i = 0; i <= classLength; i++) {
if (
this.dom.body.getElementsByClassName(val)[i]?.innerHTML != undefined
) {
this.dom.body.getElementsByClassName(val)[
i
].innerHTML = this.dom.body
.getElementsByClassName(val)
[i]?.innerHTML?.replace(/[®]/gi, '<sup>®</sup>');
}
}
}
}
}
Other.component.ts
import { AppComponent } from '../../app.component';
export class IndustryComponent implements OnInit {
constructor(private appComponent: AppComponent) { }
ngAfterContentChecked(): void {
this.appComponent.setHTMLElements();
}
}

angular2 #Input EventEmitter update view

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

Categories