Angular 7 SSR, App component ngOnInit() called multiple times - javascript

I have an angular 7 application with about 30 modules. I am fetching user details in app.component.ts to use it throughout the application but I suddenly noticed that user details API runs multiple times on 1 load.
After putting logs in ngOnInit() in my app.component.ts I found that logs are printing multiple times but it's happening only on server-side rending on the browser it renders only once.
Any idea why ngOnInit() calls more than once??

Concept of Angular SSR is that on 1st load of page/URL it renders through server and then transfer data on client side. So Technically it calls all the components, services exist on that page/URL twice.
You can use condition isPlatformBrowser and isPlatformServer method, so you can render only required part on server side. We render only specific thing on SSR which are related to SEO optimisation.

The ngOnInit() hooks only once after all directives are instantiated. If you have subscription inside ngOnInit() and it's not unsubscribed then it will run again if the subscribed data changes. In your case when loading API calls via SSR on the server side, the server does not always wait for the response to come and probably that can be a reason for multiple logs. A suggestion will be to delegate this on the client side using isPlatformBrowser. This is what it looks to me based on the investigation of general SSR flow.

I am not sure if this solved yet, but here is one suggestion:
Add the subscribe part in your component itself and remove it from your function which makes HTTP call:
ngOnInit() {
this.loadData();
}
loadData(){
this.my_service.getData().subscribe(data => this.userDetails = data);
}
And in your service, you can have a plain HTTP call:
getData() {
return this.http.get('../your/url')
.map((res:Response) => res.json());
}
I hope this helps.

Related

How to share data between components in angular

I use observable to send a value from one component to another (data is sent to the subscriber after clicking on this other component, i.e. via subject) I subscribe in another component and everything works fine until I refresh the page, after refreshing the page the component is recreated and after recreation the subscriber has no data as he did not go through the first component.How can I solve the problem?
I tried using rxjs operators as shareReplay but it didn't work like shareReplay
As your Angular app is destroyed and rebuilt when the page is refreshed, unfortunately you will lose all user state that is not saved somewhere. This is a common problem in building UIs so there are a number of tools available to combat this
Strategy:
Store your user state when an important change is made. This is called persisting state
Fetch and reapply your saved state on reload. This is called hydrating state
Options:
Persist to local storage and check for local storage values on reload to hydrate with
Persist within the users URL (simple values only), e.g. modifying the URL in some way which can be checked on reload. Assuming you are dealing with a single page, query parameters or fragments may be the way to go
Persist to a database via a POST/PATCH call and perform a GET request on reload to check for values to hydrate with
None of these methods are inbuilt into an RxJS operator (as far as I know) but we can easily leverage RxJS to achieve any of the above strategies with little effort. The tap operator is often used specifically to handle side effects, i.e. operations which should happen as a coincidence of an RxJS emission. That is precisely what we want here, in simple terms:
"If the subject emits a value, also trigger an operation which
persists the user state"
"On page load, check for any available saved user state and emit via the
relevant subject, hydrating the observables which the components will consume"
See example implementation below
tab.service.ts
type TabType = 'first' | 'second'
#Injectable({
providedIn: 'root'
})
export class TabService {
tabSelectedSubject: BehaviorSubject<TabType> = new BehaviorSubject<TabType>('first')
tabSelected$: Observable<TabType> =
this.tabSelectedSubject
.pipe(
tap(tab: TabType) => {
// ... your persist code here
this.saveTab()
},
distinctUntilChanged()
)
constructor() {
// ... your hydrate code here
this.fetchAndApplyTab();
}
saveTab(): void {
localStorage.setItem('tab', tab)
}
fetchAndApplyTab(): void {
const savedTab: TabType | null = localStorage.getItem('tab');
if (savedTab) {
this.tabSelectedSubject.next(savedTab)
}
}
}
In this case, we are exploiting the fact that our service is:
A singleton, so only loaded once per app (i.e. provided in the app root)
The service will be instantiated in the first component that loads which also injects it
This allows us to put our fetchAndApplyTab() logic in tab.service.ts's constructor and keep the code self-contained. However, depending on your use case, you may instead want to run fetchAndApplyTab() from your component manually itself.
This is happening because everything is in memory, and on page refresh all is lost, due the fact that angular app is re-initializing. You need to persist the state, for example write it into local storage, for this you could use "tap" operator from rxjs. And also in loading you could read data from localstorage end emit-it, for this you could use app_initializer hook.
there are 2 days majority to pass data between components
If both components are interconnected it means the parent or child
relationships then you can pass data with input-output decorators.
you can use the common service to share data between 2 components.
In SPA application if you refresh the browser then all in memory objects and observables are not present you need to again go back to the screen where it will be initialize.

Reactive approach + Subject service in angular

iam trying to use reactive approach in my angular app, but i am not quiet sure, that i am using it correctly. So i have a few questions to clarify it.
In my app i have global status service(its subject service), which holds some basic state of the app - month and workload id selected by user(user can switch it in navbar).
On market page i display offers, which user can apply for. To get data i need, iam using observables from market service with async pipe - no manual subscription.
Here is part of market service:
export class MarketService {
//subject with updates
private prefChangesSubj=new Subject<MarketOffer>();
//observable which loads data from API, everytime user changes status.
get loadedEvents(){
return this.statusService.appStatus.pipe(
switchMap(status=>this.getOffers(status.selectedMonth)),
shareReplay({refCount:true}));
}
//observable consumed by component - loaded Events + changes made by user
get events(){
return this.loadedEvents.pipe(
switchMap(initEvents=>this.prefChangesSubj.asObservable().pipe(
startWith(initEvents),
scan((events:{offers:MarketOffer[],dayPrefs:MarketOffer[],month:Date},update:MarketOffer)=>{
....
return events;
}
}
getOffers(date:Date){
return this.httpClient.get(....);
}
And now my questions:
Is it Ok, to have these combined observables(loadedEvents, events) in service? Or they should be combined in component?
How to handle errors when iam using async pipe? For example in loadedEvents getter, iam using switchmap to call getOffers, which gets data from API. How to handle error if http call fails? I can use catchError, but than component wouldnt be notified about error. But i must catch this potential error cause otherwise it will break the whole observable and new data wont be loaded later. How to solve this problem?
Is the approach to create combined observable from loadedEvents and changes subject correct? Or how it should be done using reactive approach?
I have searched for articles on this topic, but most of them doesnt cover problems like error handling. So i would be grateful even for links to some good articles or example apps, so i can read more about this.
thx and sorry for long post :)

View source not displaying dynamic content

I am making very simple next js application, where everything is working fine but except the view source.
I am making a promise and there is a delay in retrieving content and after when those content loaded and if I view source (ctrl + u) in chrome, I couldn't get those dynamic content loaded in the source.
So it is reproduceable in the link,
Step 1) Just click on the codesandbox link: https://3re10.sse.codesandbox.io
Step 2) After that choose view source (ctrl + u), and it gives page like,
Here you could clearly see that there is no element with text My name is Jared and all other text which is intended to be there but it is not.
Only Loading... text is available in page source which comes on page load.
The entire application working code is available here: https://codesandbox.io/s/nextjs-typescript-template-u8evx
Please help me how could I reflect all the dynamic content in view source in Next Js application.
I could understand that this is due to behaviour of async .. But really I couldn't understand the way to overcome this and display the dynamic content once loaded.. Please help me, I am stuck with this for very long..
A big thanks in advance..
You're explicitly telling React to fetch a user on a client-side.
function Profile() {
const { data, revalidate } = useSWR("/api/user", fetch);
}
If you need to prerender a user info on the server you can do it with one of the following functions:
getStaticProps (Static Generation): Fetch data at build time
getServerSideProps (Server-side Rendering): Fetch data on each request.
As you fetching a user info, I assume that it should be requested on each request, so use getServerSideProps.
const URL = 'api/user/'
export default function Profile({ initialData }) {
const { data } = useSWR(URL, fetcher, { initialData })
return (
// render
)
}
export async function getServerSideProps() {
const data = await fetcher(URL)
return { props: { initialData: data } }
}
This way you would fetch a user info on the server and give it to React with first render. Also, you would have useSWR on a client side that will periodically revalidate data.
Suggested reading:
Data fetching
If you use nextjs you must run
yarn build
and
yarn export
then you have directory 'out' with your exported static content.
Because now your example is CSR (client side rendering)
Other answers already quite clear telling you the reason why your dynamic content doesn't appear on source code.
First, there are two kinds of rendering: server side and client side.
Server side / SSR is when your server render your app and then send it to the client (browser).
Client side / CSR is when your app reach the browser, it will be rendered again (but this time only render what is necessary if you have activated SSR, which NextJS has as default).
If you want your dynamic content to be appear at the source code, you should call your api on the server side like #Nikolai Kiselev has mentioned.
NextJS provides function getServerSideProps()(for the component level) to be used if developers want to fetch info on the server side.
If you put your profile() function as a page, you could also use getInitialProps() function to fetch your api from server side.
Please take a look on NextJS doc, they have given the examples you need.

How to make only one API request when using React SSR?

My application has to make an API call, that returns an array of items that gets styled and rendered to the screen. To make my SSR rendering work, I have to make the API request on server of course, but when using the SSR you also have to rerender (using ReactDOM.hydrate) on the client and I'm making the 2nd API request in here.
Since the API request takes more than 2 seconds, doing it 2 times is so inefficient. Is there a workaround around that by only doing 1 request and someway using the data on both server and client?
This is how you fetch data on a server and reuse it on client - straight from the redux documentation. If you are not using redux you will need to implement something very similar.
To answer the latter question - how to avoid making another request on the client? You just need to check if the data is there :)
componentDidMount() {
if(!this.props.isDataFetched) {
this.props.fetchData();
}
}
or maybe...
componentDidMount() {
if(this.props.data.length === 0) {
this.props.fetchData();
}
}

Calling same service multiple times, sign of bad design?

I'm in the process of building out a fairly large Angular app and I've stuck to the design of building 'thin' controllers. My controllers don't try to do too much, they are each focused on one piece of functionality within my app.
There, however, is certain data that is 'shared' between controllers. I aim to avoid using $rootScope and instead rely on services to share data and 'state'.
When looking in the 'Network' tab of Chrome Dev Tools I notice certain services being called half a dozen times. So my question is, is this bad design? Are multiple calls to the same service within an Angular app not the 'Angular way' to do things? Note: these service calls take ~ 20ms each, so clearly not much of a performance hit...so far.
I'd suggest reducing the number of unnecessary HTTP requests that you're making for two reasons:
In production environments, these HTTP requests may take more time to complete when factors such as network latency or server load are taken into account; and
will delay the loading of any other assets such as images (assuming that they're coming from the same domain).
An approach that I've used when dealing with the scenario that you've described is to cache the response/data from the API in the service. So, on subsequent calls to the service, the data can be pulled from the cache rather than the API.
See a brief example below:
angular.module('app', ['ngResource'])
.factory('Post', ['$resource', function($resource) {
var posts = [];
var service = {
all: all
}
return service;
function all() {
// if cached posts exist, return those. Otherwise, make call to external API.
if (posts.length > 0) {
return posts.$promise;
} else {
posts = $resource('http://localhost/api/v1/posts.json').query();
return posts.$promise;
}
}
}]);
Note: you'll also have to consider resetting your cache however this would be dependent on your application logic.
Hope this helps!
L
In this case you should look to use $cacheFactory to reduce service calls.
Are you talking about REST services? Are you making $http calls in order to share data and state between controllers?
Why not use service/factory in Angular?
What you need is
1. DataCache service/factory - which will store your response from server
2. A directive - to call these services. include it in your different views
3. Now inside your service which is responsible for making http call first check if data is available in cache if yes return promise of stored data (you can use $q.when) if not make service call.
I have mentioned point 2 since I am assuming inside your various controller you must be doing something like AbcFactory.getItem().then() to avoid duplication of this code as you never know when the requirement will change since change is the only constant during development ;)

Categories

Resources