Image getting cached in Angular - javascript

I am using angular version 9. I have 2 components. 1 to search a car make, model and 2nd component to load the details of the car that is selected from component 1. I pass the image link for the car from the car that is selected from component 1 to component 2 and load that image in component 2. This works fine..
When I browse to some other page in my application and again go to the component 1 and select a different car and go to component 2, the image of the previously selected car shows for around 1-2 seconds and then my new image is shown. This looks like a browser caching problem. I have tried to load a loader image initially till my new image completes loading and then show the newly loaded image but it does not work. What I have tried is as below.
<img *ngIf="isLoading" [src]="loadingimage.png" />
<img [hidden]="isLoading" [src]="carModelImage" (load)="isLoading = false"/>
What I have noticed is, the moment component 2 is opened the (load) event on the img tag fires and changes the isLoading value to false even without waiting for the new carModelImage to load and thus the previous image shows for some time.
My component code looks like below.
#Component({
selector: 'app-request-summary',
templateUrl: './request-summary.component.html',
styleUrls: ['./request-summary.component.scss']
})
export class RequestSummaryComponent implements OnInit, OnDestroy {
public carModelImage: string = '';
public isLoading: boolean = true;
constructor() {
this.carModelImage = '';
this.isLoading = true;
}
ngOnInit() {
this.requestSummaryService.carData
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((data) => {
this.carModelImage = data[0].carModelImage ? data[0].carModelImage
:'http://nocarimage.png';
}
}
ngOnDestroy() {
this.isLoading = true;
this.carModelImage = '';
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
I am struglling due to this issue since quite a long. Any help will be highly appreciated.

Well, I don't have much to go on here, but I'll share a simple pattern with you.
When your image is no longer being displayed null it out.
For example somewhere in your code you probably write this:
carModelImage = "your-image";
When you are sure you no longer need this var anymore, null it out.
carModelImage = null;
Post more code if you want help specific to your issue.

How do you assign carmodelImage variable . The carmodelImage is responsible for showing the previous image, if you could nullify it on destroy may be it should help
Assuming you said ngondestroy is being called

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 get access to nativeElements on ngOnInit?

Suppose in my angular code i have access to a html element by viewChild('someDiv') or constructor(private elem: ElementRef){}.Whenever my angular component gets loaded i want access to that elements some property so i can store it in variable.Ex- i want to get the elements width as soon as that component gets loaded so my code is
#ViewChild('someDiv') someDiv: ElementRef;
someDivWidth;
ngOnInit(){
someDivWidth = this.someDiv.nativeElement.clientWidth;
}
and my html code
<div #someDiv>some text</div>
But this is giving error like nativeElement is undefined.So how can i solve this?My basic need is as soon as this component loads i want to set that div's width in variable without triggering
any function.
Update:
So using constructor(private elem: ElementRef){} isn't giving any error and i can get access to width by
ngOnInit(){
this.someDivWidth = this.elem.nativeElement.querySelector('.someCssSelector');
}
But suppose i have some products data which i am fetching from backend and storing in an array in ngOnInit lifecycle hook and in my template creating multiple div's depending upon the products data array using lifecycle hook.In that case if i want to get all the product div's currently i am doing like
products = [];
items;
ngOnInit(){
this.productService.getProducts().subscribe(products => {
this.products = products
})
this.itmes = this.elem.nativeElement.querySelectorAll('.each-product');
console.log(this.items)
}
this is giving me a empty nodeList.I thought at the time of setting this.items my products don't gets loaded from backend thats why its giving me empty array or nodeList but putting that last two lines in subscribe() like
ngOnInit(){
this.productService.getProducts().subscribe(products => {
this.products = products
this.itmes = this.elem.nativeElement.querySelectorAll('.each-product');
console.log(this.items)
})
}
and my html code is
<div>
<div *ngFor='let product of products' class='each-product'>...</div>
</div>
is still giving me an empty array or nodelist.What to do??
You should do it in the ngAfterViewInit method of the angular life cycle.
ngAfterViewInit(){
this.someDivWidth = this.elem.nativeElement.querySelector('.someCssSelector');
}
Angular uses its algorithms to detect changes in data and respond to the view.If you want to opreate the DOM after the request was completed, you may not be able to get it because the view hasn't been updated yet, and you can understand that Angular also needs time to update the view.
Offer two solutions
Use ChangeDetectorRef to perform change detection manually
use setTimeout
stackblitz-demo
ngOnInit lifeCycle is invoked once when component instantiated and called right after the properties checked for the first time, and before its children checked.
If You want to be sure that view has been loaded (#ViewChild depends on view to be rendered) you should use ngAfterViewInit which is called after component view and it's children view are created and ready.
UPDATE: If you need to access viewChild in ngOnInit you have to set { static: true }

Ionic + Firebase - Real time database not updating view

I have a really weird problem with my application. In order for the real time database to display, I have to interact with the application. 2 months ago I did not have this problem, the information appeared normally, but now, I have to click on a button or something in order for the view to update.
I made a quick GIF here:
If you pause just as I click the <- back button you'll see what I mean.. https://i.gyazo.com/44b450aa502dc7f37e5a5feea19824c6.mp4
I don't know what I did to make this happen..
Here is a code example from my application:
.ts:
if(fromClick) { this.subscription.off(); }
this.postFeed = new Array();
this.subscription = this.database.database.ref('/posts/'+this.locationId)
.orderByChild('score')
.limitToLast(10);
this.subscription.on('child_added', (snapshot) => {
this.postFeed.push(snapshot.val())
});
.html:
<ng-container *ngFor="let post of postFeed.reverse(); let i = index">
<post [postData]="post"></post>
</ng-container>
So this code above used to work perfectly, but now I have to interact with my app for it to display on the screen, even though the postFeed console logs perfectly fine. The data shows up in the console exactly when I request it, but it doesn't show on screen.
Any ideas? As I said, it used to work fine. Any help would be great! Thank you
For anyone else who has this problem, NgZone was the answer for me.
I imported it:
import { NgZone } from '#angular/core';
added it to the constructor:
public zone: NgZone,
and then wrapped it around my firebase code:
this.zone = new NgZone({});
if(fromClick) { this.subscription.off(); }
this.postFeed = new Array();
this.subscription = this.database.database.ref('/posts/'+this.locationId)
.orderByChild('score')
.limitToLast(10);
this.subscription.on('child_added', (snapshot) => {
this.zone.run(() => {
this.postFeed.push(snapshot.val())
}):
});
and this worked wonderfully for me. I couldn't find the actual cause of the issue, but I just wrapped the NgZone code around all my firebase functions and it worked like it used to. If anyone else has more information that would be great.

Angular binding #input behavior not working propertly

I'm having some issues with the Angular EventEmitter and angular #Input.
My app has 3 components: 2 components (TableComponent and MapComponent) that do not interact between them, and an extra component that is like the father of those two (BodyComponent).
TableComponent defines the following #Input
_oportunidades: Item[];
#Input() set oportunidades(oportunidades: Item[]){
debugger;
this._oportunidades = oportunidades;
this.dataSource = new MatTableDataSource<Item>(this._oportunidades);
this.dataSource.paginator = this.paginator;
}
The MapComponent defines:
#Output() event_emitter_for_items_filtered_by_polygon = new EventEmitter<string[]>();
send_new_map_information_to_body(){
this.event_emitter_for_items_filtered_by_polygon.emit(this.items_filtered_by_polygon);
}
add_function_that_sends_filtered_items_to_body_after_polygon_is_draw(){
var self = this;
google.maps.event.addListener(this.drawingManager, 'polygoncomplete', function(polygon) {
self.filter_items_by_polygon(polygon);
self.remove_markers_from_map();
polygon.setPath([])
self.send_new_map_information_to_body();
});
}
When the procedure send_new_map_information_to_body is triggered. Sends the modified data to the BodyComponent. The BodyComponent catches it without errors.
The BodyComponent html is shown here:
<div class="analysis">
<app-mapa (event_emitter_for_items_filtered_by_polygon)="items_filtered_by_polygon($event)" [items]="map_data"></app-mapa>
<app-tabla [oportunidades]="oportunidades_filtradas"></app-tabla>
</div>
The procedure items_filtered_by_polygon modifies the oportunidades_filtradas variable defined in the BodyComponent. Until now, everything is ok.
items_filtered_by_polygon($event){
this.oportunidades_filtradas = []
}
The variable oportunidades_filtradas is binded to the oportunidades variable in the TableComponent (as shown in the BodyComponent html), when items_filtered_by_polygon method changes oportunidades_filtradas the #Input() set oportunidades(oportunidades: Item[]) is not triggered. So, no changes are shown in my TableComponent.
When the app starts, and data is distributed through the components, everything works as expected. Just in this case, when trying to modify the TableComponent content as explained, nothing happens.
In the devtools console of chrome, no errors are shown. And the flow of the app does not feel strange, just nothing happens.
Sometimes, we thought that the modifications are being done, but maybe they are terrible delayed? Maybe is some kind of async issue?
I'm kind of new in Angular and maybe I am not understanding something. All other binds in my app are working...
What do you think? Any help is welcome!
Thanks!
This sounds like there may be a change detection issue happening. Depending on your change detection strategy things like this can happen. Try using ChangeDetectorRef.detectChanges() in your items_filtered_by_polygon function to see if that's the issue. If it works you can leave it there or remove it and use an observable for the input that isn't triggering.

Angular2 change detection not working after callback

I am quite new to angular2 and I have a problem with the change detection.
At the loading of my page, I need to call some API in order to get the information to construct my web page. What I do is that when I receive this information (which is contained in an array) I want to iterate through it using *ngFor. This is my code for a course component.
import {Component,Input} from 'angular2/core';
import {courseCompDiagram, sepExInWeeks} from "../js/coursesTreatment.js";
import {getSampleWeeks} from "../js/courseMng.js";
#Component({
selector: 'course',
directives:[Exercises],
template: `
<div class="course">
<h2>{{aCourse.name}}</h2>
<div class='diag-container row'>
<div id="Completion{{aCourse.name}}"></div>
<div *ngFor="#week of weeks"> {{week.weekNb}} </div>
</div>
</div>`
})
export class Course{
//This is inputed from a parent component
#Input() aCourse;
this.weeks = [];
ngAfterViewInit(){
//I call this method and when the callbacks are finished,
//It does the following lines
courseCompDiagram(this.aCourse, function(concernedCourse){
//When my API call is finished, I treat the course, and store the results in weeks
this.weeks = sepExInWeeks(concernedCourse.course.exercises);
});
//This is not supposed to stay in my code,
//but is here to show that if I call it here,
//the weeks will effectively change
this.weeks = getSampleWeeks();
}
}
So first of all, I would like to know if it's normal that angular2 doesnt detect the fact that this.weeks changed.
Then I don't know if I should us the ngAfterViewInit function to do my work in. The problem is that I began doing that because in my courseCompDiagram I need to use jquery to find the div containing the id Completion[...] and modify it (using highcharts on it). But maybe I should do all this at some other point of the loading of the page ?
I tried using ngZone and ChangeDetectionStrategy as stated in this topic but I didn't manage to make it work on my case.
Any help is appreciated, even if it doesn't completely solves the problem.
export class Course{
//This is inputed from a parent component
#Input() aCourse;
this.weeks = [];
constructor(private _zone:NgZone) {}
ngAfterViewInit(){
//I call this method and when the callbacks are finished,
//It does the following lines
courseCompDiagram(this.aCourse, (concernedCourse) => {
//When my API call is finished, I treat the course, and store the results in weeks
this._zone.run(() => {
this.weeks = sepExInWeeks(concernedCourse.course.exercises);
});
});
//This is not supposed to stay in my code,
//but is here to show that if I call it here,
//the weeks will effectively change
this.weeks = getSampleWeeks();
}
}
You should use arrow functions to be able to use lexical this, as described below:
courseCompDiagram(this.aCourse, (concernedCourse) => {
// When my API call is finished, I treat the course,
// and store the results in weeks
this.weeks = sepExInWeeks(concernedCourse.course.exercises);
});
As a matter with raw callbacks, the this keyword doesn't correspond to your component instance.
See this link for more hints about the lexical this of arrow functions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions.
Otherwise I have a sample comment regarding your code. You should leverage observables for your HTTP calls. It doesn't seem the case in your code as far as I can see...

Categories

Resources