delay before binding template angular - javascript

this is kind of relate to ExpressionChangedAfterItHasBeenCheckedError again.
in my situation, I have a parameter 'data' which I recover from ajax call.
this data is used to create child via #input directive. I wish the binding only occurs when data is defined. but I have no idea how to do that with angular
export class UserComponent{
data: any
constructor(
private userService: UserService){
this.userService.getUser()
.subscribe(result => {
this.data = result;
}
}
);
}
because the way it works, angular will display an ExpressionChangedAfterItHasBeenCheckedError, which I understand why, but how to ask angular to wait for the callback to be done and data to be != from undefined before start binding and stuff, the goal is to have some child initialized with the real value coming from the database.
If I should wait for the end of the cycle before binding "real" data, it's fine, but how to do it without having this error, (and please without using setTimeout as it looks a lot of rubbish !).
Thanks
<!--the template if it matter --><somechildtag [data]="data" ></somechildtag>

Load your component with ng-container.
For example:
<div *ngIf="data?.length > 0">
<ng-container *ngComponentOutlet="myChildComponent;
ngModuleFactory: childComponentsModule;"></ng-container>
</div>
So, you will render your child only if your data object is populated.
In your component:
private childComponentsModule: NgModuleFactory<any>;
constructor(private compiler: Compiler){
this.childComponentsModule = compiler.compileModuleSync(ChildComponentsModule);
}
You can see a more detailed example of how to load dynamically a component here:
Angular2: Use Pipe to render templates dynamically
Angular's documentation:
https://angular.io/docs/ts/latest/api/common/index/NgComponentOutlet-directive.html

Related

How do I handle undefined properties from the html in my Angular application before I receive data from my controller?

When my Angular component HTML renders. I will get lots of errors that look like this
ERROR TypeError: Cannot read properties of undefined (reading 'someProperty')
In my HTML page, I have lots of bindings that look like this
<h1>{{event.title.toUpperCase()}}</h1>
In most components I call a service to fetch data from my controller in the 'ngOnInit()' and I know that one solution to prevent this error before the data comes back from my controller is to make the property nullable like this
event?.title
but this doesn't seem like a practical solution, especially if I have dozens on the page.
What I have tried -
Making them nullable with a '?' but that doesn't seem correct, especially if I always doing it
Setting the object type being returning from the controller to a 'any' (ex. event: any), but in some cases it still shows an error
QUESTION - What's the best solution to handle this so I don't keep getting these errors until my page data loads?
Should I have some conditional like *ngIf="event" and hide the page until the data is received and show some loading indicator while waiting? Or is some way to jsut prevent these error from showing in the console?
EDIT - here is the code I use to fetch data from the controller and set it to 'event'
// component
event: any;
// ngOnInit()
this.eventService.getEvent(this.id).pipe(take(1)).subscribe(response => {
this.event = response;
}, error => {
console.log(error);
});
// service
getEvent(id: number): Observable <any> {
return this.http.get<any>(this.baseUrl + 'events/' + id);
}
Defining a boolean variable and checking if data has been loaded is the best approach:
// component
event: any;
loaded: boolean;
// ngOnInit()
this.eventService.getEvent(this.id).pipe(take(1)).subscribe(response => {
this.event = response;
this.loaded = true;
}, error => {
console.log(error);
});
In your HTML page, place your HTML tags inside a section or a div tag as follows:
<section *ngIf="loaded">
// your HTML tags here
</section>
In this way you do not keep getting undefined errors and bindings will happen only when data is really loaded. That's it!
I would wrap your affected html into something like
<ng-container *ngIf="event">
...
</ng-container>
When reading your description i'd also suggest to use the observable that is your http-call as an async-binding in your template instead of mapping it to a concrete object "event".
To provide you some more information on that however more code is required from your side.
[EDIT]
Your JS:
// class member
event$: Observable<any> = of(undefined);
// whereever
this.event$ = this.eventService.getEvent(this.id).pipe(take(1),
catchError(error => {
console.log(error);
//or return something else
return throwError(error);
}));
Binding in HTML:
<ng-container *ngIf="(event$ | async) as event">
...
</ng-container>
[/EDIT]

How to call body onload JavaScript functions in Angular 10

We are migrating old JavaScript projects into Angular 10.
There are many JavaScript functions calling from body onload. What is the best way to call such functions in Angular 10? Please help.
In Angular 10 you can call a javascript function on body load using ngOnInit() function
call the the function inside the ngOnInit() function .
like this
export class DashboardComponent implements OnInit {
constructor() {}
ngOnInit(): void {
this.checkNotifiactionToken(); //this is your custom function which is you want to call on body load
}
}
There should be a specific approach, when you are migrating old js body onload function. And you have multiple functions as well.
To keep it in mind angular component solution, I would suggest 1st to find which are the function required for which components.
let's suppose you have five function are in body onload.
One function required before even loading the app to get some configuration, move it to app initializer, you need to setup APP_INITIALIZER in angular.
Other two function required to load the 1st paint or home page. Move them to their respective components or you can use route resolver, if you have routing or ngOninit
The key point here to for better performance, in Angular we have multiple way to do it. You need to find that match where things required and fit.

Angular - recalculate a variable on every change

I have a variable that stores the available cars at any moment. Is there a way to automatically re-evaluate this function on every change?
Just using this.carFactory.available in this case is not a solution, because this example I'm showing is simplified - the real calculation in my project is alot more complex.
calculateAvailableCars(){
this.carFactory.available.forEach(function(item){
this.availableCars.push(car.id);
}.bind(this));
}
How could I do this in Angular 2? In Angular JS there was the possibility to $watch a function.
I could of course manually call this function everytime something changes, but it would be nice not to have to call this function in every part of the application that can change the data.
Using template function reference with auto change detection
You can use this function output on template:
carOutput(): cars[] {
this.calculateAvailableCars()
return this.availableCars;
}
and use output on template:
<p>My car ratio is {{ carOutput() }} </p>
However this will trigger very aggressive change detection strategy on this variable. This solution is the simpliest one, but from engineering perspective rather worst: consumes tons of unnecessary function calls. One note, that hosting element must not be set to detect changes onPush.
Separate data model to parent component and pass as property to child
You can store car list display in separate component, and pass new car array as input property to this component:
<car-display [cars]="availableCars"></car-display>
Then you can set changeDetetcion policy in this component to onPush, and each time input property bind to availableCars will change, <car-display> will re-render.
If update relays on some host binding
If some external host action is triggering new cars calculation, then hostBinding may help:
#hostListener(`hover`) recalculateCars() {
this.calculateAvailableCars()
}
And finally, (because you describe your use case quite cryptically, without many details, thus I'm scratching all possible scenarios) if some external component action shall trigger re-calculation, you can hook to ngLifecycle ngOnChanges() if for example external input property change shall re-trigger cars calculation.
In other words and summing all that up, it depends who and from where triggers changes, that shall re-trigger available cars recalculation.
And very important, see an answer from #chiril.sarajiu, because what we are trying to work around here can be handled automatically by single observable. This requires additional setup (service, provide observable to components, e.c.t.) but it's worth.
--- EDIT ---
If each variable change shall retrigger data
As OP clarified, that changes are related with model bound to component. So another option with mentioned by #marvstar is using set, where each model variable change will retrigger fetching function:
modelSchangeSubject: Subject<Model> = new Subject<Model>();
ngOnInitt() {
this.modelSchangeSubject
.subscribe((v: Model) => {
this.calculateAvailableCars()
})
}
/* Rest of controller code */
set modelBounded(v: Model) {
this.modelSchangeSubject.next(v);
}
You need RxJS. What you do is you create a data service, which will store an Observable (in my case a BehaviorSubject, which is mostly the same, but in my case I start with a value).
export class DataService {
private dataStorage$ = new BehaviorSubject(null); //here is the data you start with
get getDataStorage() {
return this.dataStorage$.asObservable(); // so you won't be able to change it outside the service
}
set setDataStorage(data: any) {
this.dataStorage$.next(data);
}
}
Then you subscribe to this data changes everywhere you need to:
constructor(private dataService: DataService){}
ngOnInit() {
this.dataService.getDataStorage.subscribe((data) => this.calculateAvailableCars(data));
}
calculateAvailableCars(){
this.carFactory.available.forEach(function(item){
this.availableCars.push(car.id);
}.bind(this));
}
Read more about best practices of using RxJS in Angular, as there can be quite a bit of pitfalls and problems.
Try using setter and getter.
private _YourVariable:any;
public set YourVariable(value:any){
this._YourVariable = value;
//do your logik stuff here like. calculateAvailableCars
}
public get YourVariable():any{
return this._YourVariable ;
}

Angular 4 - how to run a directive method only after its parent has fully rendered

I use Angular 4. I have a directive which is a button, inserted into the parent's template like this:
<button (emitPdfUrl)="getInvoicePdfUrl($event);" pdfButton [pdfEl]="pdf">
<fa name="file-pdf-o"></fa> Download PDF
</button>
How can I run the method getPdf() from the directive when the parent has fully rendered? I need this because this function will then get the innerHtml from some other parts of her parent's template. What happens right now is, the directive's function (getPdf()) gets the half displayed template, ie. the template without the loaded variables of the parent, and thus, the innerHTML is not complete with loaded data inside, so the function takes the "empty" template. Thanks for any advice guys! :)
It is parent component's responsibility to provide a method that will be able to follow the desired execution order.
This can be achieved with a pair of RxJS subjects:
private componentInitSubject = new AsyncSubject().ignoreElements();
public pdfUrlSubject = new ReplaySubject(1);
constructor() {
this.pdfUrlSubscription = this.componentInitSubject
.concat(this.pdfUrlSubject)
.subscribe(url => this.getInvoicePdfUrl(url));
});
ngOnAfterViewInit() {
this.componentInitSubject.complete();
}
getInvoicePdfUrl(url) {
// starts to receive urls only after completion of componentInitSubject
}
And values can be emitted to the subject instead of calling getInvoicePdfUrl directly:
<button (emitPdfUrl)="pdfUrlSubject.emit($event)" pdfButton [pdfEl]="pdf">
pdfUrlSubscription should be unsubscribed on component destroy.

What is the best way to handle errors in angular 1.5+ in a smart/dumb structure?

I saw a lot of documentation about smart and dumb components.
For example:
angular.module('myList', []).component('myList', {
bindings: {
items: '<',
onDelete: '&'
},
templateUrl: 'my_list.template.html' });
When I click on a button with an "onDelete" binding, the smart component execute a function in a service, the service call to the server/API and refresh the items object on the above dumb component. Ok, cool.
My question is, what happend if the server returns an error? I'd like to show that error inside the my_list.template.html template.
Do I need to add an '<error' binding?
Another doub. If I have a form on myList component, and I want to reset the form (and many other things...) when the server finish the onDelete execution, what is the more elegant way to do this?
(to do it I return a promise in the onDelete function but I don't like it much)
var promise=vm.onDelete({item:item});
promise.then(...);
Thanks

Categories