Calling a locally declared function in Angular's ngOnChanges - javascript

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

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

Custom component property binding triggers multiple times

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.

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

How to call code inside ngAfterViewInit again in angular 2

In my angular component I have ngAfterViewInit method which contains some statements that I want to execute after the view is initialized cause it contains some DOM manupulations.
My question is when some parameter is changed I want to run the code inside ngAfterViewInit.
You could define an EventEmitter in the constructor, subscribe to it in the ngAfterViewInit() function to update those values, emit it in the ngAfterViewInit() function, and then emit it again every single time you want to call it in subsequent areas of the component. Here is an example:
import { EventEmitter } from '#angular/core';
export class MyComponent implements AfterViewInit {
public myEvent: EventEmitter<void>;
public constructor() {
this.myEvent = new EventEmitter<void>();
}
public ngAfterViewInit(): void {
// This is how you call the function to do what you want to do with your DOM manipulations below. You can also call this exact function even from the HTML, if you wish to do so (see HTML example).
this.myEvent.emit();
this.myEvent.subscribe(
() => {
// Do whatever actions that you need to do here to perform your DOM manipulations.
},
(err: Error) => console.error(err);
);
}
public emitMyEvent(): void {
this.myEvent.emit();
}
}
<my-component (click)="myEvent.emit()"></my-component>
<!-- or -->
<my-component (click)="emitMyEvent()"></my-component>
If you want to execute those statements on every changes than it is better to write those in ngAfterViewChecked().From the docs:
A lifecycle hook that is called after the default change detector has completed checking a component's view for changes
A callback method that is invoked immediately after the default change detector has completed one change-check cycle for a component's view.
So it will be called on every subsequent changes.
More information can also be found on the Lifecycle Hooks#AfterView docs
If your parameter is available as an Observable, you can just subscribe to it in the ngAfterViewInitmethod. If your parameter is not yet available as Observable, I suggest you take a look at the BehaviourSubject class. With this, you can control when the Observable will emit a new value + It will be triggered with the last value when you subscribe to it
You declare ordinary class methords in the class body, and later define them in the context of ngAfterviewInit.
here's a simple use case example:
export class ViewtestComponent implements OnInit, AfterViewInit{
#ViewChild('someElementMewantedToDoAction') elRef: ElementRef;
constructor() { }
ngOnInit(): void {
}
changeVal;
ngAfterViewInit() {
this.changeVal= (useME) => {
// some action
this.elRef.nativeElement.innerText++;
}
}
Later, use the method in template as
// Do Action= Button value will increment 11, 12 13 ... on each button click.
<button class="button success" (click)="changeVal($emit)>10</button>
I have solved my problem by implementing OnChanges and call ngAfterviewInit for changes other than the firstchange.This way I will make sure that the view is initiallized. By the way the variable subjected to change(changed_var) holds data used in DOM manipulation.
ngOnChanges(changes: SimpleChanges){
if(!changes.changed_var.isFirstChange()){
this.ngAfterViewInit();
}
}

Angular 4 - "Expression has changed after it was checked" error while using NG-IF

I setup a service to keep track of logged in users. That service returns an Observable and all components that subscribe to it are notified (so far only a single component subscribe to it).
Service:
private subject = new Subject<any>();
sendMessage(message: boolean) {
this.subject.next( message );
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
Root App Component: (this component subscribes to the observable)
ngAfterViewInit(){
this.subscription = this._authService.getMessage().subscribe(message => { this.user = message; });
}
Welcome Component:
ngOnInit() {
const checkStatus = this._authService.checkUserStatus();
this._authService.sendMessage(checkStatus);
}
App Component Html: (this is where the error occurs)
<div *ngIf="user"><div>
What I'm trying to do:
I want every component (except the Root App Component) to send the users logged-in state to the Root App Component so I can manipulate the UI within the Root App Component Html.
The issue:
I get the following error when the Welcome Component is initialised.
Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'true'.
Please note this error occurs on this *ngIf="user" expression which is located within Root App Components HTML file.
Can someone explain the reason for this error and how I can fix this?
On a side note: If you think theres a better way to achieve what I'm trying to do then please let me know.
Update 1:
Putting the following in the constructor solves the issue but don't want to use the constructor for this purpose so it seems it's not a good solution.
Welcome Component:
constructor(private _authService: AuthenticationService) {
const checkStatus = this._authService.checkUserStatus();
this._authService.sendMessage(checkStatus);
}
Root App Component:
constructor(private _authService: AuthenticationService){
this.subscription = this._authService.getMessage().subscribe(message => { this.usr = message; });
}
Update 2:
Here's the plunkr. To see the error check the browser console. When the app loads a boolean value of true should be displayed but I get the error in the console.
Please note that this plunkr is a very basic version of my main app. As the app is bit large I couldn't upload all the code. But the plunkr demonstrates the error perfectly.
What this means is that the change detection cycle itself seems to have caused a change, which may have been accidental (ie the change detection cycle caused it somehow) or intentional. If you do change something in a change detection cycle on purpose, then this should retrigger a new round of change detection, which is not happening here. This error will be suppressed in prod mode, but means you have issues in your code and cause mysterious issues.
In this case, the specific issue is that you're changing something in a child's change detection cycle which affects the parent, and this will not retrigger the parent's change detection even though asynchronous triggers like observables usually do. The reason it doesn't retrigger the parent's cycle is becasue this violates unidirectional data flow, and could create a situation where a child retriggers a parent change detection cycle, which then retriggers the child, and then the parent again and so on, and causes an infinite change detection loop in your app.
It might sound like I'm saying that a child can't send messages to a parent component, but this is not the case, the issue is that a child can't send a message to a parent during a change detection cycle (such as life cycle hooks), it needs to happen outside, as in in response to a user event.
The best solution here is to stop violating unidirectional data flow by creating a new component that is not a parent of the component causing the update so that an infinite change detection loop cannot be created. This is demonstrated in the plunkr below.
New app.component with child added:
<div class="col-sm-8 col-sm-offset-2">
<app-message></app-message>
<router-outlet></router-outlet>
</div>
message component:
#Component({
moduleId: module.id,
selector: 'app-message',
templateUrl: 'message.component.html'
})
export class MessageComponent implements OnInit {
message$: Observable<any>;
constructor(private messageService: MessageService) {
}
ngOnInit(){
this.message$ = this.messageService.message$;
}
}
template:
<div *ngIf="message$ | async as message" class="alert alert-success">{{message}}</div>
slightly modified message service (just a slightly cleaner structure):
#Injectable()
export class MessageService {
private subject = new Subject<any>();
message$: Observable<any> = this.subject.asObservable();
sendMessage(message: string) {
console.log('send message');
this.subject.next(message);
}
clearMessage() {
this.subject.next();
}
}
This has more benefits than just letting change detection work properly with no risk of creating infinite loops. It also makes your code more modular and isolates responsibility better.
https://plnkr.co/edit/4Th7m0Liovfgd1Z3ECWh?p=preview
Declare this line in constructor
private cd: ChangeDetectorRef
after that in ngAfterviewInit call like this
ngAfterViewInit() {
// it must be last line
this.cd.detectChanges();
}
it will resolve your issue because DOM element boolean value doesnt get change. so its throw exception
Your Plunkr Answer Here Please check with AppComponent
import { AfterViewInit, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import { MessageService } from './_services/index';
#Component({
moduleId: module.id,
selector: 'app',
templateUrl: 'app.component.html'
})
export class AppComponent implements OnDestroy, OnInit {
message: any = false;
subscription: Subscription;
constructor(private messageService: MessageService,private cd: ChangeDetectorRef) {
// subscribe to home component messages
//this.subscription = this.messageService.getMessage().subscribe(message => { this.message = message; });
}
ngOnInit(){
this.subscription = this.messageService.getMessage().subscribe(message =>{
this.message = message
console.log(this.message);
this.cd.detectChanges();
});
}
ngOnDestroy() {
// unsubscribe to ensure no memory leaks
this.subscription.unsubscribe();
}
}
Nice question, so, what causes the problem? What's the reason for this error? We need to understand how Angular change detection works, I'm gonna explain briefly:
You bind a property to a component
You run an application
An event occurs (timeouts, ajax calls, DOM events, ...)
The bound property is changed as an effect of the event
Angular also listens to the event and runs a Change Detection Round
Angular updates the view
Angular calls the lifecycle hooks ngOnInit, ngOnChanges and ngDoCheck
Angular run a Change Detection Round in all the children components
Angular calls the lifecycle hooks ngAfterViewInit
But what if a lifecycle hook contains a code that changes the property again, and a Change Detection Round isn't run? Or what if a lifecycle hook contains a code that causes another Change Detection Round and the code enters into a loop? This is a dangerous eventuality and Angular prevents it paying attention to the property to don't change in the while or immediately after. This is achieved performing a second Change Detection Round after the first, to be sure that nothing is changed. Pay attention: this happens only in development mode.
If you trigger two events at the same time (or in a very small time frame), Angular will fire two Change Detection Cycles at the same time and there are no problems in this case, because Angular since both the events trigger a Change Detection Round and Angular is intelligent enough to understand what's happening.
But not all the events cause a Change Detection Round, and yours is an example: an Observable does not trigger the change detection strategy.
What you have to do is to awake Angular triggering a round of change detection. You can use an EventEmitter, a timeout, whatever causes an event.
My favorite solution is using window.setTimeout:
this.subscription = this._authService.getMessage().subscribe(message => window.setTimeout(() => this.usr = message, 0));
This solves the problem.
To understand the error, read:
Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error
You case falls under the Synchronous event broadcasting category:
This pattern is illustrated by this plunker. The application is
designed to have a child component emitting an event and a parent
component listening to this event. The event causes some of the parent
properties to be updated. And these properties are used as input
binding for the child component. This is also an indirect parent
property update.
In your case the parent component property that is updated is user and this property is used as input binding to *ngIf="user". The problem is that you're triggering an event this._authService.sendMessage(checkStatus) as part of change detection cycle because you're doing it from lifecycle hook.
As explained in the article you have two general approaches to working around this error:
Asynchronous update - this allows triggering an event outside of change detection process
Forcing change detection - this adds additional change detection run between the current run and the verification stage
First you have to answer the question if there's any need to trigger the even from the lifecycle hook. If you have all the information you need for the even in the component constructor I don't think that's the bad option. See The essential difference between Constructor and ngOnInit in Angular for more details.
In your case I would probably go with either asynchronous event triggering instead of manual change detection to avoid redundant change detection cycles:
ngOnInit() {
const checkStatus = this._authService.checkUserStatus();
Promise.resolve(null).then(() => this._authService.sendMessage(checkStatus););
}
or with asynchronous event processing inside the AppComponent:
ngAfterViewInit(){
this.subscription = this._authService.getMessage().subscribe(Promise.resolve(null).then((value) => this.user = message));
The approach I've shown above is used by ngModel in the implementation.
But I'm also wondering how come this._authService.checkUserStatus() is synchronous?
I recently encountered the same issue after migration to Angular 4.x, a quick solution is to wrap each part of the code which causes the ChangeDetection in setTimeout(() => {}, 0) // notice the 0 it's intentional.
This way it will push the emit AFTER the life-cycle hook therefore not cause change detection error.
While I am aware this is a pretty dirty solution it's a viable quickfix.
Don't change the var in ngOnInit, change it in constructor
constructor(private apiService: ApiService) {
this.apiService.navbarVisible(false);
}

Categories