Custom component property binding triggers multiple times - javascript

i have noticed that when you use a custom component with a input property and you bind a function to it this function is called lots of times.
E.g.
<some-tag [random-input]="randomFunction()"></some-tag>
and in the class
private randomFunction() {
console.log('Called!');
return true
}
if you run something simple as this you will see in the console a few dozens of 'Called!' logs.
In my project the randomFunction makes a call to the database, so this is pretty anoying.
Does anyone knows why is this happening?

Angular runs this with every cycle, trying to check for updated value, that's why you see so many messages in the log.
For this reason it is not good practice to have ts functions as inputs to component.
You can for example make a call to the server/database in constructor, OnInit or OnChanges, store the result to local variable and make that variable as input to component. Something similar to this:
export class MyComp {
dbResult: any;
constructor(http: HttpClient) {
http.get('/my/api/call').subscribe(result => {
this.dbResult = result;
});
}
....
}
..and in HTML:
<some-tag [random-input]="dbResult"></some-tag>
As a sidenote, having that function marked as private will eventually fail during ng build --prod

Angular needs to check if the value has changes otherwise it can't update the value inside the component.

Related

What causes the failure send a variable from a component to a service, in this Angular 11 app?

In an Angular 11 app, I have a service and a component and I need to bring a variable (that contains search items) from the component to the service.
The service:
export class NavigationService implements OnDestroy {
private getSearchResult$: Observable<any>;
private getSearchResultSubject = new Subject<any>();
constructor() {
this.getSearchResult$ = this.getSearchResultSubject.asObservable();
}
public getSearchResult() {
console.log('searchResults: ', this.categoryComponent.searchResult$);
this.getSearchResultSubject.next();
}
}
The component:
export class CategoryComponent implements OnInit {
public searchResult$: Observable<SearchResult>;
constructor(
private _navigationService: NavigationService,
) {
super();
this._navigationService.getSearchResult();
}
}
There are no compilation errors.
The problem
For a reason I have been unable to understand, the line this.categoryComponent.searchResult$ throws this error:
TypeError: Cannot read properties of undefined (reading 'searchResult$')
Where is my mistake?
There's some mistakes happening with your project now, some are just minor nitpicks but also a couple of implementations being wrong.
If we first look at your service.file
constructor() {
this.getSearchResult$ = this.getSearchResultSubject.asObservable();
}
In angular you usually dont use the constructor to do any real logic or assignment we use the onInit lifecycle for that, however services doesnt have the capability of implementing that so I can understand your thinking in this regard.
For your get method you first console.log the components variable / classmember which isnt available to the service hence the error (also described by brk). You also just set the next of the subject with no value and not returning anything.
public getSearchResult() {
console.log('searchResults: ', this.categoryComponent.searchResult$);
this.getSearchResultSubject.next();
}
Looking at your component file:
constructor(
private _navigationService: NavigationService,
) {
super();
this._navigationService.getSearchResult();
}
The same issue here with the constructor handling logic where it should be moved to a lifecycle method or assigned to a variable. I've also never seen super() used within a constructor before in angular and dont think its necessary to have but if it doesnt do harm then its up to you to keep it or not.
Im not entirely sure what it is you need to do but i've created a stackblitz where you from the component update the value of the subject in the service and then subscribe to its values to present it back in the component. Let me know if its something along those lines you are looking for or if its something else and we can work it out together.
https://stackblitz.com/edit/angular-ivy-ghl84q?file=src/app/app.component.ts
You are trying to call the component inside the service which should not be the case. . Component should call the service but not the other way. Also inside the service there is no instance of categoryComponent.
So this line
console.log('searchResults:'this.categoryComponent.searchResult$); is bound to throw error.
Remove the console.log and also getSearchResult in service file is not returning anything. So the component will get undefined

Calling an angular component method from classic HTML

I'm trying to call a function defined within an angular component.ts from another javascript library
The library supports a snapin, where I can define HTML Elements, which will be rendered (they are not part of angular).
Here is my html (not in a component's template)
<button onclick='ping()'>PING Button</button>
<button onclick='pong()'>PONG Button</button>
How can I, from the html above, call the pong component method defined my component.ts
import { Component, OnInit, AfterViewInit, Inject, ChangeDetectionStrategy, ChangeDetectorRef } from '#angular/core';
function ping() {
alert('PING PING');
}
#Component({ ...
})
export class Component implements OnInit, AfterViewInit { ...
pong() { //This is the method I want to call from outside of the component's template
alert('pong pong');
}
}
I tried, but it will not work
<button (click)="pong()">PONG Button</button>
But I have no idea to call the "pong()" function normally
Thanks!
If you really need this, you can make the method available in the window object
component.ts
constructor()
{
window['pong'] = () => this.pong();
}
pong()
{
alert ('pong inside component')
}
Then in your html, you can use old style event handler
<button onclick="pong()">Pong</button>
Here is a stackblitz demo
Note: If you have several instance of the same angular component implementing this solution, you'll only have once instance of the method. You could save them all to an array if needed, but you'll need to know which one to call
I guess you want to use a non-angular library from angular, which has a global callback. Be warned that this can lead to problems, because you have to manage the lifecycle of the non-angular thing from angular.
From the angular template, you can only call methods on the component class, and you can't call a global callback. You can however create a method on the component class, and call the global callback from there.
There's one more thing before that's possible: typescript doesn't know about your global callback, so you have to explictily declare it, see the example. This tells typescript that there's something that is created outside of typescript, so it will let you call it.
import { Component } from '#angular/core';
declare const libMethod: (any) => any;
#Component({
selector: 'my-app',
template: `
<button (click)="myMethod($event)"></button>
`,
styleUrls: []
})
export class AppComponent {
public myMethod(param) {
libMethod(param);
}
}
If you plan to use that library from multiple of your angular components, then you might want to create a service, declare the global callback only in that, and create a method on the service. That way, this somewhat hacky declaration will not be littered all over your code, but contained to a single place. It also makes your life easier, if you upgrade/replace this library.
Answers to questions in the comments:
TBH I don't completely understand the situation. (Who calls the backend, when it returns the HTML? You from Angular, or the lib? Does the lib process the HTML? Or what does it do?)
Some suggestions: create a global singleton service, which puts up one of its methods to the window (dont' forget to bind it if you use this inside the method) to serve as a callback for the lib. When the lib calls it with the data, regardless of who/when actually triggered the lib to do its thing, the service stores the data in a subject, and the service also provides an observable of the data (maybe with a shareReplay(1) so that the consumers always get something).
With that, actually displaying the data is fairly easy, you can just use the async pipe, and not care about how/when the data got there in the first place, and don't have to sync the component's lifecycle with the service.
Also, you probably need to use https://angular.io/api/platform-browser/DomSanitizer#bypasssecuritytrusthtml but I am not sure about that, since I never had to inject HTML. Speaking about which...
Important security notice: if you inject HTML from outside of angular, and that is hijacked, you just opened up your page to all kind of nasty cross site scripting things.

Calling a locally declared function in Angular's ngOnChanges

I know it's a rather basic question but i just can't seem to find a solution to this. Here it is:
I have an Angular Component, and in that component i have a function.
export class RolesListComponent implements OnInit, OnChanges {
#ViewChild(DxDataGridComponent) dataGrid: DxDataGridComponent;
ngOnChanges(changes: SimpleChanges): void {
this.refresh();
}
refresh(){
this.dataGrid.instance.refresh();
}
}
Calling the this.refresh() inside the onchanges doesn't work and gives an error that refresh is undefined.
What can i do to execute either the code inside the function directly in the onchanges, or the function itself.
According to the doc
ngOnChanges Respond when Angular (re)sets data-bound input properties
Simply put, if you have any #input properties and if u want to detect any changes to those properties, you can use ngOnChanges. Since i don't see any input properties in you component that may be why, ngOnchanges doesn't get execute

Angular - dynamically destroying a component, *ngIf and ComponentRef

I'm making a pop-up component that I want to use in several of my other components, so I made a popup.service that enable the component to be loaded through *ngIf inside other components. This is creating a problem for me since the PopupComponent is a separate entity and I'm unsure how to pass data from the child component(PopupComponent) to its respective parents.
Atm the loading looks like this in ParentComponent.ts:
public openPopup(order_id: string, invoice_id: string): void{
this.load_popup=this.popupService.openPopup(order_id, "selected_order", invoice_id, "selected_invoice");
}
And ParentComponent.html:
<app-popup *ngIf="load_popup"></app-popup>
And it loads like a charm, the problem is in closing it. The close button is located on the PopupComponent, is there an efficient way to have the Child Component (PopupComponent) to affect a variable in the Parent Component ie. ParentComponent.load_popup=false?
My other thought was dynamically loading the component, however I have no idea on how to do that. I was fidgeting around with using the PopupService and putting something like this in it:
import { Injectable, ComponentRef } from '#angular/core';
import {PopupComponent} from '../popup/popup.component';
#Injectable({
providedIn: 'root'
})
export class PopupService {
popup_ref: ComponentRef<PopupComponent>
constructor(
) { }
//Implemented in orderoverviewcomponent, invoicecomponent, and placeordercomponent
public openPopup(id1:string, storage_label1:string, id2:string, storage_label2:string): Boolean{
if (id1){
localStorage.setItem(storage_label1, JSON.stringify(id1));
}
if (id2){
localStorage.setItem(storage_label2, JSON.stringify(id2));
}
this.popup_ref.initiate(); //this line is a made up example of loading the component
return true;
}
public closePopup(storage_label1: string, storage_label2:string): Boolean{
if(storage_label1){
localStorage.removeItem(storage_label1);
}
if(storage_label2){
localStorage.removeItem(storage_label2);
}
this.popup_ref.destroy();
return false;
}
}
Where this.popup_ref.destroy(); would ideally destroy PopupComponent, but when I did that I got a "cannot read property of undefined" on the popup_ref, I'm having trouble declaring it, the syntax seems a bit tricky.
The problem also remains that i need a function to load the component, the opposite of .destroy(), if this is possible I would much prefer it over loading and destroying with *ngIf.
Edit: Partially solved it by just using a boolean in the service as the trigger for *ngIf, is there a way to do a function load and destroy on a component still?
You can bind an EventEmitter() to your component to invoke a function in the parent component.
<app-popup [onClose]="load_popup = false" *ngIf="load_popup"></app-popup>
Then inside of your app-popup component:
#Output onClose = new EventEmitter();
public closePopup(/* code emitted for brevity */) {
/* code emitted for brevity */
this.onClose.emit(); //Call the parent function (in this case: 'load_popup = false')
}
It's important to know that you can pass entire functions to the bound function, and you can even pass variables back to the parent from the child:
[onClose]="myFunction($event)"
this.onClose.emit(DATA HERE);
As an aside, since you're using Angular; I would suggest looking into using Modals for popup dialogue boxes. You can see a good example here:
https://ng-bootstrap.github.io/#/components/modal/examples

Store doesn't fire changes to the async pipe

In my app.component.html I create a custom component (contact-table component) that should get a new value (Account) to present after I update the in the store the connected user to be someone else.
app.component.html:
<contact-table [contacts]="connectedAccount$ |async"></contact-table>
app.component.ts:
export class AppComponent implements OnInit
{
private connectedAccount$: Observable<Account>;
constructor(private store:Store<any>) {
this.connectedAccount$ = this.store
.select(state=> state.connectedAccountReducer.connectedAccount);
this.connectedAccount$
.subscribe(account1=> {
//the app prints any new account that comes up. It's working here.
console.log(account1);
});
}
public ngOnInit() {
this.store.dispatch({
type: updateConnectedAccount,
payload: {
connectedAccount: ACCOUNTS[0]
}
});
}
}
The subscribtion in AppComponent class works great and fires any update that comes up.
The problem is that the async pipe in app.component.html does not send the new account to the contact-table component. I think he doesn't get notified for any updates.
I tried to see what is being sent to the contact-table component,
and I saw he only get one value in the first creation of the contact-table component and its undefined. Thats not possibole becouse my reducer function creates an empty Account object for the initial state of my app.
Is there anything you notice that I missed?
Check and make sure you are using Object.assign to create the changes to your state. It shouldn't return a mutated state. Redux/ngrx and the async pipe detect changes when the object hash changes (or at least this is my understanding). I've run into this problem when I wasn't creating a brand new object but accidentally mutating the existing state and returning it.
To quote the redux site -> http://redux.js.org/docs/basics/Reducers.html
We don’t mutate the state. We create a copy with Object.assign(). Object.assign(state, { visibilityFilter: action.filter }) is also wrong: it will mutate the first argument. You must supply an empty object as the first parameter. You can also enable the object spread operator proposal to write { >...state, ...newState } instead.
I also had a async issue with RC2 so if you aren't using RC3 or above I'd recommend upgrading.
Not saying this is it but it is the most likely candidate from my experience. Hope this helps.
Try using the json pipe just to see if the view is getting anything from that store select.
<span>{{connectedAccount$ |async | json}}</span>
You can include ngrx-store-freeze - the problem with mutable state is easy to address.
Another way of how I often debug the dispatching of Actions is to introduce an Effect for logging purpose. This helps to identify problems as well.

Categories