How to run function after a component became visible in Angular 8? - javascript

I want to load datas only if the user see the specific component which load the datas. So don't load before the component is visible.
I have this template:
<button (click)="showMyCompontente()">Click me</button>
<app-other-component *ngIf="show"></app-other-component>
With this TypeScript code:
export class MyComponent implements OnInit {
show = false;
ngOnInit() {
}
showMyCompontente() {
this.show = !this.show;
}
}
And the point is here:
#Component({
selector: 'app-other-component',
})
export class OtherComponent implements OnInit {
ngOnInit() {
this.load();
}
load() {
// needs to load datas only if the user see the component
}
}
How to achive in the OtherComponent to start the this.load() only if the component is visible to the user? I want to reload datas again if the user hide the component and show it agian.
I need a solution inside the component to detect itself is became visible or disappear. That's because I have many compontents, calling eachothers in many variations.
Which Angular lifecycle hooks fires only when the user shows the component?

I would try:
Adding an input property with type of boolean to OtherComponent component.
Passing value of show to the input property, then inside the OtherComponent component, use ngOnChanges to detect any change in the input property and call load() accordingly.

Related

Angular 10 - View doesn't update when model is modified

Angular 10.1.6, TypeScript 4.0.5
Hello
Context
In my webapp, I have 3 components.
Component A stores an Array<Image>
export class ComponentParentA implements OnInit {
images: Array <Image> ;
addImageToList(newImage: Image) {
this.images.push(newImage)
}
}
The Component C displays the list of images.
export class ComponentChildC implements OnInit {
#Input() images: Array <Image>;
}
The component C is called from the Component A html template like this :
<ComponentChildC [images]="images"></ComponentChildC>
Component B is in charge to contact my API, add an image which has been selected previously. The API return an Image object, which correspond to my model. The component emit the Image so the ComponentA add the returned image to its Array (Calling addImageToList)
addImage is an Observable return by this.http.post (HttpClient)
export class ComponentChildB implements OnInit, AfterViewInit {
#Output() eventCreateImage : EventEmitter<Image>;
addImage(data) {
this.service.addImage(data).subscribe((image) => {
this.eventCreateImage.emit(image)
})
}
}
Component A calls Component B like this :
<ComponentChildB (eventCreateImage)="addImageToList($event)"></ComponentChildB>
Problem
The Component B add an image when I click on a button. This click trigger the addImage function. All the component work correctly and the image is save on my server. The API returns the image and the latter is correctly stored in the Array in Component A. However, the view of the component C is not updated and the newly created image doesn't appear. If I click a second time on the button, a new image is stored. This time, the previous image, which I couldn't see previously, appears correctly. The new image don't.
I would like the image to appear directly.
What I already did
I saw similar issues on the web.
First, I tried to replace this.images.push(newImage) by this.images = this.images.concat(newImage) in addImageToList function. It doesn't work.
Next, I saw that, because images is an array, Angular doesn't detect any change when I add a new Image in my array. I made my Component C implements the OnChange interface. Effectively, the ngOnChange function is not called.
I saw here that I could try to reload my component manually So I made my Component C implement DoCheck. Something strange appeared. When I click on the button and I do console.log(this.images) in ngDoCheck in component C, I see that the list is correctly updated. But the image still doesn't appear. So I try to call ChangeDetectorRef methods like markForCheck and detectChanges to notify the component of the change, but it didn't work.
If you have any idea where my problem may be coming from, I would be grateful if you could help me
I didn't test but i think it's cause strategy detection. You can try 3 differents solutions (I can't test now but normaly it will be work).
PS: sorry for my english
1/ in your object #Component add changeDetection in your components
#Component({
selector: 'app-component',
template: `...`,
changeDetection: ChangeDetectionStrategy.OnPush
})
then in your function addImageToList() replace your code by
addImageToList(newImage) {
this.images = [...this.images, newImage]
}
2/ in your component C replace your #Input by
#Input() set images(value: Array <Image>) {
myImages = value
}
the properties binding in html will be myImages in this context
3/ You can change manualy the strategy detection with the service ChangeDetectorRef. Add this service in your constructor component A then in your function add :
constructor(
private cd: ChangeDetectorRef
){}
addImageToList(newImage: Image) {
this.images = [...this.images, newImages];
this.cd.markForCheck();
}
If this solutions don't work look the documentation of detection strategy in Angular or try to mix 1 and 2 together

How to store value in common file and get the same value in other component in angular 6

I am working on a project Where I have a dropdown in one component (Header component) and I am assigning a default value to it in an API call on first component load. Example: value = 1
Problem is when I change the dropdown value Example: value = 2 and try to navigate to another component, the header component loads each and every time I navigate to any other component as it is commonly used in structure and the value again changes back to the default assigned Example: value = 1
I have tried to achieve this by creating service file and using subscribe in it.
And call it. But each time I navigate to another component the Header component loads and the value again changes.
Is there any way that I can achieve this.
I think your main component could have wrong structure.
If your main component contains HeaderComponent and router, it should look something like this:
<app-header></app-header>
<section>
<router-outlet></router-outlet>
</section>
<app-footer></app-footer>
Then navigation in your application via Router should not cause re-render of HeaderComponent.
If your HeaderComponent gets data in ngOnInit then it should be instantiated only once:
#Component({
selector: 'app-header',
template: 'header.component.html'
})
export class HeaderComponent implements OnInit {
myData: [];
constructor(private myService: MyService) {}
ngOnInit() {
this.myService.getData()
.subscribe(result => this.myData = result);
}
}

Manipulating a reusable component based on input

I have created a reusable component and using it twice inside a component. But I need two buttons, that can manipulate the component individually.
In my case the button for component1 should not update both the instance of the component.
I think I'm doing something wrong by design, but any suggestion will help me.
Stackblitz
Reusable Component:-
import { Component, OnInit,Input } from '#angular/core';
import { AppService } from '../app.service';
#Component({
selector: 'app-reusable',
templateUrl: './reusable.component.html',
styleUrls: ['./reusable.component.css']
})
export class ReusableComponent implements OnInit {
#Input() items:any;
constructor(
private service:AppService
) { }
ngOnInit() {
this.service.addFruit.subscribe(()=>{
this.items.unshift({fruit:'Blackberry'});
});
}
}
Usage:-
<button type="button" (click)="Add()">Add Component1</button>
<app-reusable [items]="fruitList1"></app-reusable>
<hr/>
<button type="button" (click)="Add()">Add Component2</button>
<app-reusable [items]="fruitList2"></app-reusable>
I want to update only one instance of reusable component at once.
Either instance 1 or 2.
You have to let the service know which component you are calling from.
Try the changes I made in the demo.
app.component.html
<button type="button" (click)="Add(1)">Add Component1</button>
<app-reusable [items]="fruitList1" [componentNumber]="1"></app-reusable>
app.component.ts:
Add(componentNumber:number){
this.service.addFruit.next(componentNumber);
}
reusable.component.ts:
#Input() componentNumber: number;
ngOnInit() {
this.service.addFruit.subscribe((x) => {
if (x == this.componentNumber)
this.items.unshift({ fruit: 'Blackberry' });
});
}
Working Stackbiltz
More cleaner approch would be simply pass the component instance and call related method so create a method in your reusable component something like
addFruit(){
this.items.unshift({fruit:'Blackberry'});
}
Modify your add method to get component as instance and call this method
Add(instance:ReusableComponent){
instance.addFruit();
}
Then add hash to seprate each instance and pass instance in method
<button type="button" (click)="Add(instance1)">Add Component1</button>
<app-reusable [items]="fruitList1" #instance1></app-reusable>
Working demo
Each instance of ReusableComponent is subscribed to the Subject addFruit. Clicking on the button will update Subject value which will trigger all subscriptions.
In order to avoid this, you will need to add a filter in a subscription which ignores values from other components by adding some when doing this.service.addFruit.next();. You can do filtering with RXJS filter operator. https://rxjs-dev.firebaseapp.com/api/operators/filter
Another idea is to create a subscription for each component in service and save them in some map/object in service. When a component requests a subscription from service it would add an entry to map which would be subjectId: new Subject(). You would return that new subject for the component to subscribe. Instead of doing next() directly you would call service method addNewFruit(subjectId: string, newFruit: string): void.
Map would be:
{
'firstId': Subject,
'secondId': Subject,
}
The most simple idea for this case is to use ViewChild and call method addFruit from the parent component.
Instead of having subscription of APP service in the reusable component , on click of button you should modified the input provided to your components. If you update the fruitList1 or fruitList2 at a time then it would not update the another instance of the component.

Inject data into component in Bootstrap Modal on Angular 4

I'm using the #ng-bootstrap/ng-bootstrap which adds Bootstrap components to Angular. You can inject components into an opened Modal, so the injected component makes up the body/content of the opened modal.
I have a list of Player. When I click a player, I want to open a Modal with the clicked player's data inside of it. I can open a Modal with the injected PlayerComponent like this.
constructor(
private modalService: NgbModal
) { }
openPlayerModal() {
const modalRef = this.modalService.open(PlayerComponent)
}
The question is... How do I inject additional data to the component to let it know what player's data to fetch? E.g. I might have an OnInit interface on the PlayerComponent that fetches data from an API based on an ID supplied by the modalService.open() call.
It seems like #ng-angular/ng-angular allow you to touch the component instance after it has been instantiated. So you can then modify the values.
So solution would be:
constructor(
private modalService: NgbModal
) { }
openPlayerModal() {
const modalRef = this.modalService.open(PlayerComponent)
modalRef.componentInstance.player_id = 1;
}
And then make the component use an #Input() decorator on player_id.
export class PlayerComponent {
#Input() player_id;
...
}

Angular.js 2: Access component of a directive

Consider the following snippet of Parent's template:
<div *ngFor= "let event of events" >
<event-thumbnail [theEvent] = 'event'></event-thumbnail>
</div>
Also event-thumbnail component definition is:
export class EventThumbnailComponent{
intoroduceYourself(){
console.log('I am X');
}
}
In Parent component class, I want to iterate over all generated event-thumbnail elements, access the component beneath each, and call introduceYourself function on single one of them.
You want to use the #ViewChildren() decorator to get a list of all instances of a specific component type within the view:
class ParentComponent implements AfterViewInit {
#ViewChildren(EventThumbnailComponent)
eventThumbnails: QueryList<EventThumbnailComponent>;
ngAfterViewInit(): void {
// Loop over your components and call the method on each one
this.eventThumbnails.forEach(component => component.introduceYourself());
// You can also subscribe to changes...
this.eventThumbnails.changes.subscribe(r => {
// Do something when the QueryList changes
});
}
}
The eventThumbnails property will be updated whenever an instance of this component is added to or removed from the view. Notice the eventThumbnails is not set until ngAfterViewInit.
See the docs here for more information:
https://angular.io/docs/ts/latest/api/core/index/ViewChildren-decorator.html
Your child component should have #Input() theEvent to get access to the event you are passing. Then you can use the following lifecycle hook:
ngOnInit(){
introduceYourself(){
console.log('I am X');
}
}

Categories

Resources