ngOnChange is not called when the parent updates the child - javascript

i want to know how ngOnChanges callback works. so i have added it to observe changes in a prpoperty annotated with Input decorator as follows:
#Input() postsToAddToList: Post[] = [];
now, when I compile the code i add some values that causes change in the property annotated with #Input, but that does not cause the the ngOnChanges callback to be called and executed. please see logs shown in the screen-shot posted below.
i want to see the logs in the ngOnChanges displayed in the browser.
please let me know what prevents the ngOnChanges to be invoked and called correctly
app.component.ts:
import { Component } from '#angular/core';
import { Post } from './post-create/post-create.component';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'binding2';
postsArray: Post[] = [];
onReceiveSubmittedEvtEmitter(post: Post) {
this.postsArray.push(post);
console.log("onReceiveSubmittedEvtEmitter->: post.title: " + post.title);
console.log("onReceiveSubmittedEvtEmitter->: post.content:" + post.content);
}
}
app.component.html:
<app-post-create (onPostSubmittedEvtEmitter)="onReceiveSubmittedEvtEmitter($event)"></app-post-create>
<app-post-list [postsToAddToList]="postsArray"></app-post-list>
post-list.component.ts:
import { Component, Input,OnInit, OnChanges, SimpleChanges,Output, EventEmitter } from '#angular/core';
import { Post } from '../post-create/post-create.component';
#Component({
selector: 'app-post-list',
templateUrl: './post-list.component.html',
styleUrls: ['./post-list.component.css']
})
export class PostListComponent implements OnInit {
constructor() {}
#Input() postsToAddToList: Post[] = [];
ngOnInit(): void {}
ngOnChanges(changes: SimpleChanges) {
for (let changedProperty in changes) {
if (changes.hasOwnProperty(changedProperty)) {
console.log("ngOnChanges->: changedProperty: " + changedProperty);
console.log("ngOnChanges->: changedProperty:" + changedProperty);
switch(changedProperty) {
case 'postsToAddToList':
console.log("ngOnChanges->: changes[changedProperty].previousValue: " + changes[changedProperty].previousValue);
console.log("ngOnChanges->: changes[changedProperty].currentValue):" + changes[changedProperty].currentValue);
break;
}
}
}
}
}
post-list.component.html:
<!-- post-list.component.html -->
<h3>List</h3>
<ng-container *ngIf="postsToAddToList.length; else elseTemplate">
<ul class="list-group">
<li class="list-group-item" *ngFor="let post of postsToAddToList; let i = index">
<h5>{{i+1}}) {{post.title}}</h5>
<p>
{{post.content}}
</p>
</li>
</ul>
</ng-container>
<ng-template #elseTemplate>
<div class="alert alert-danger" role="alert">
No Post Found!
</div>
</ng-template>
error screen-shot:

As u are using push() in your parent component, ngOnChanges will not be invoked in the child component. Instead of using push() you can reassign value to postsToAddToList every time there is a change in it.

Related

How do I pass data from one component to another (New Browser Tab) in angular?

I'm new to angular and I don't know how to pass data between two components using routers. This is my first component view,
when I press view report button I need to call another component with the first component data. This is my first component view report click button code.
<button type="button" (click)="onFuelViewReport()" class="btn btn-success ">
<b>view Report</b>
</button>
when clicking the button it calls onFuelViewReport() function in the first component and using this function it opens the second component view with a new browser window (tab). What I want is to pass data from the first component to the second component from here. Please help me to do this.
onFuelViewReport() {
this.router.navigate([]).then(result => {
window.open("/pages/view-report", "_blank");
});
}
If you want to share data from child component to parent component, you can use #Output event emitter or if your are trying to share data within unrelated components, you can use BehaviourSubject (This also works in case of parent to child component communication and vice versa).
Child to Parent: Sharing Data via Output() and EventEmitter
parent.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-parent',
template: `
Message: {{message}}
<app-child (messageEvent)="receiveMessage($event)"></app-child>
`,
styleUrls: ['./parent.component.css']
})
export class ParentComponent {
constructor() { }
message:string;
receiveMessage($event) {
this.message = $event
}
}
child.component.ts
import { Component, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
template: `
<button (click)="sendMessage()">Send Message</button>
`,
styleUrls: ['./child.component.css']
})
export class ChildComponent {
message: string = "Hola Mundo!"
#Output() messageEvent = new EventEmitter<string>();
constructor() { }
sendMessage() {
this.messageEvent.emit(this.message)
}
}
Unrelated Components: Sharing Data with a Service
data.service.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class DataService {
private messageSource = new BehaviorSubject('default message');
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
parent.component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from "../data.service";
#Component({
selector: 'app-parent',
template: `
{{message}}
`,
styleUrls: ['./sibling.component.css']
})
export class ParentComponent implements OnInit {
message:string;
constructor(private data: DataService) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message)
}
}
sibling.component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from "../data.service";
#Component({
selector: 'app-sibling',
template: `
{{message}}
<button (click)="newMessage()">New Message</button>
`,
styleUrls: ['./sibling.component.css']
})
export class SiblingComponent implements OnInit {
message:string;
constructor(private data: DataService) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message)
}
newMessage() {
this.data.changeMessage("Hello from Sibling")
}
}
The window.open looks absolutely awful. Use this.router.navigate(['/heroes']);.
So if I understand correctly you have a list of items and when you click on one of the items, the details page of that item should open?
Best practice is to allow the detail route to have a property to set. the Angular Routing & Navigation page is very complete. It shows that you should use :id - { path: 'hero/:id', component: HeroDetailComponent }. When you open the detail page, you get the id variable and then get the data for it.

execute a function evertime ngFor is finished displaying data

I am trying to call a function everytime my ngFor is done loading data from my API.
but the callback is only triggering on first load of the ngFor.
how can I execute the callback everytime my ngFor is changed;
I used this answer: https://stackoverflow.com/a/38214091/6647448
here is what I have so far...
HTML
<button class="btn" (click)="changeDate()"></button>
<div *ngFor="item of items; let last = last">
<div>{{item}}{{last ? ngForAfterInit() : ''}}</div>
</div>
TS
this.ngForIsFinished = true;
ngForAfterInit() {
if (this.ngForIsFinished) {
this.ngForIsFinished = false;
}
}
changeDate() {
// this is where i trigger my ngFor to change its content
}
The person who answered said that you just need to set the ngForIsFinished back to true but I am having a hard time where to set it on my code.
Try to use ChangeDetectionStrategy.OnPush and last variable of ngFor. Because ChangeDetectionStrategy.Default always checks methods, so fooMethod(...) will be called multiple items:
<div *ngFor = "let title of iterated; let i=index; let last=last">
{{last ? fooMethod(last) : '' }}
</div>
yourComponent.ts:
import { Component, ChangeDetectionStrategy } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
name = 'Angular 4';
iteratedData = [
{"title":"test1","description":"foo","name":"name1"},
{"title":"test2","description":"foo","name":"name2"},
{"title":"test3","description":"foo","name":"name3"},
{"title":"test4","description":"foo","name":"name4"}
];
fooMethod(v) {
if (v)
console.log(`This is from the method ${v}`);
}
}
UPDATE:
After you've loaded data you should call slice method as Angular 2 change detection strategy doesn't check the contents of arrays or object. So try to create a copy of the array after mutation:
this.iteratedData.push(newItem);
this.iteratedData = this.iteratedData.slice();
OR:
constructor(private changeDetectorRef: ChangeDetectorRef)
And you can call a method markForCheck to trigger a changeDetection:
this.iteratedData.push(newItem);
this.changeDetectorRef.markForCheck();
Here is the example:
Component:
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular 5';
data:any = [];
ngAfterViewInit(){
this.changeDate();
}
ngForAfterInit(valuue) {
alert('FOR LOOP COMPLETED : '+ valuue);
}
ChangeDate(){
let i = 0;
setInterval(()=>{
this.data=[];
for(var j=0; j<10; j++){
this.data.push("TEST DATA "+ j+" SET AT : "+i);
};
i+=1;
},7000);
}
}
html:
<div *ngFor="let item of data; let last=last;">
{{item}} {{last? ngForAfterInit(item) :''}}
</div>
SetInterval is like your API which will assign data.This is working for me
https://stackblitz.com/edit/pokemon-app-4n2shk

Link component tag in HTML to a component passed in constructor

I am fairly new to Angular and TypeScript in general.
In my AppComponent HTML, I inject another component with
<app-listpost></app-listpost>
But in the TypeScript, I also receive this component because my AppComponent imports my ListPostComponent in order to call a function from ListPostComponent in AppComponent.
The function simply adds an object to an array from ListPostComponent
Thus, I found out that calling that function from AppComponent works but the array with which AppComponent works is from another instance than the array from the ListPostComponent instanciated with the HTML tag.
app.component.html
<div style="text-align:center">
<h1>
{{ getTitle() }}
</h1>
</div>
<app-listpost></app-listpost>
<button class="btn btn-success" (click)="addPostToList('test title from AppComponent', 'test content from AppComponent')">Add test post</button>
app.component.ts
import { Component } from '#angular/core';
import { ListpostComponent } from './listpost/listpost.component'
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
providers:[ListpostComponent]
})
export class AppComponent {
title = 'Exercise';
constructor(private listPostComp: ListpostComponent){}
public addPostToList(newTitle, newContent){
this.listPostComp.addPostToList(newTitle, newContent);
}
getTitle(){
return this.title;
}
}
listpost.component.html
<button class="btn btn-success btn-test" (click)="addPostToList('test title from ListPostComponent', 'test content from ListPostComponent')">Add test post</button>
<div class="container">
<div class="row">
<div class="col-xs-12">
<ul class="list-group">
<app-post *ngFor="let post of getListPosts()"
[Title]="post.title"
[Content]="post.content"></app-post>
</ul>
</div>
</div>
</div>
listpost.component.ts
import { Component, OnInit, ChangeDetectionStrategy } from '#angular/core';
#Component({
selector: 'app-listpost',
templateUrl: './listpost.component.html',
styleUrls: ['./listpost.component.scss'],
changeDetection: ChangeDetectionStrategy.Default
})
export class ListpostComponent implements OnInit {
private listPosts = [
{
title: 'Post example',
content: 'Content example'
}
];
constructor() { }
ngOnInit() {
}
addPostToList(newTitle, newContent){
let newPost = {
title: newTitle,
content: newContent
}
this.listPosts.push(newPost);
console.log("Added new post with title : \"" + newTitle + "\" and content : \"" + newContent + "\"");
console.log(this.listPosts); //DEBUG
}
getListPosts(){
return this.listPosts;
}
}
The ultimate goal was that cliking the button from app.component.html would call the function from listpost.component.ts, add an object to the array and that the display from listpost.component.html would refresh with the newly added object.
It works when I click the button in listpost.component.html but not from app.component.html for the reason I explained above.
Therefore, is there a way for me to specify that I want to use the instance of ListPostComponent I receive in the constructor of AppComponent in the HTML tag <app-listpost></app-listpost> ?
I think best practice would be to create an injectable service that contains a function with your addPostToList() logic. Then create another function in your ListPostComponent that calls that service function and updates the listPosts. Then in your AppComponent, call the function from your ListPostComponent.

How to set value to parent component property when a button is clicked in Child component in Angular

I am new to Angular 7 (2+) & trying my hands on #Input & #Output. However, passing data from Parent to Child component via #Input is understood & in place.
However, very basic on the other hand passing data from Child to Parent component via using #Output concept is understood & but the implementation is not getting right.
Here is what I am trying. When a button is clicked in the
Child component, a property in the parent component should be
converted to Upper case & displayed.
ChildComponent.ts
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
})
export class ChildComponent implements OnInit {
#Input('child-name') ChildName: string;
#Output() onHit: EventEmitter<string> = new EventEmitter<string>();
constructor() { }
ngOnInit() {}
handleClick(name): void {
this.onHit.emit(name);
}}
ChildComponent.html
<h1> child works! </h1>
<button (click)="handleClick('eventEmitter')"> Click me! </button>
ParentComponent.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
title = 'my-dream-app';
customerName: string = "";
catchChildEvent(e) {
console.log(e);
}}
ParentComponent.html
<div style="text-align:center">
<app-child [child-name]="HelloChild"></app-child>
//Trying to bind to Custom event of child component here
<b (onHit)="catchChildEvent($event)">
<i> {{customerName}} </i>
</b>
No error in console or binding
From the above snippet, when the button in Child Component is clicked the parent Component's property CustomerName should get the value & displayed?
Example: https://stackblitz.com/edit/angular-3vgorr
(onHit)="catchChildEvent($event)" should be passed to <app-child/>
<app-child [child-name]="HelloChild" (onHit)="catchChildEvent($event)"></app-child>
You are emitting event from app-child component so attach the handler for app-child component to make it work.
<div style="text-align:center">
<app-child (onHit)="catchChildEvent($event)" [child-name]="HelloChild"></app-child>
//Trying to bind to Custom event of child component here
<b>
<i> {{customerName}} </i>
</b>
And within the ts file update value of cutomerName property.
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
title = 'my-dream-app';
customerName: string = "";
catchChildEvent(e) {
this.customerName = e;
}
}
You should move (onHit)="catchChildEvent($event)" to app-child in parent html:
<app-child [child-name]="HelloChild"
(onHit)="catchChildEvent($event)>
</app-child>

String interpolation in Angular 2 not displaying updated value

I'm trying to learn Angular 2, and I'm running into a weird issue while following this documentation: https://angular.io/docs/ts/latest/guide/user-input.html for binding button clicks to a method on my controller.
I started off with their click event binding example. I created a simple app with the angular cli with ng new test-app and modified it so that it contains a single button that when I click, just adds a message to the page.
I have two components, the app component and the message components shown here:
message.component.html:
<p>{{message}}</p>
message.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-message',
templateUrl: './message.component.html',
styleUrls: ['./message.component.css']
})
export class MessageComponent implements OnInit {
message: string;
constructor() {
this.message = 'some initial message';
}
ngOnInit() {
}
}
app.component.html:
<button (click)="addMessage()">Click</button>
<app-message *ngFor="let message of messages"></app-message>
app.component.ts:
import { Component } from '#angular/core';
import { MessageComponent } from './message/message.component';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
messages = [];
addMessage() {
const newMessage = new MessageComponent();
newMessage.message = 'Something else entirely';
this.messages.push(newMessage);
}
}
When I run this with ng serve, the button appears as expected, but when I click the button, only the message some initial message appears despite being given a different value by my app controller. While doing a search I found a different way to do the one-way databinding by replacing the string interpolation with: <p [innerText]="message"></p> but the result is the same.
I'm kinda at a loss here as to why it won't display the updated value of Something else entirely.
MessageComponent component should take message as #Input:
export class MessageComponent implements OnInit {
#Input() message: string;
}
AppComponent should send this input to its child like
<app-message *ngFor="let message of messages"
[message]="message.message"></app-message>

Categories

Resources