Issue
In my Angular (10) application I use data that are specific for each request. I use part of the URL domain name to make some business decisions.
In my application (just early development) I parse the URL, save this data in the public variable of the app (inside of AppComponent class) and use it when needed.
At some point, I decided to move some logic from the app.component.ts to child components. And because this data can be used by a few components, rather than parsing it many time inside of each component I’m thinking about parsing it once in the "AppComponent" and passing it down as a parameter.
Though, computational expenses on passing parameter might be higher than parsing it again so:
Question #1: is it reasonable to pass data to a component that the component might obtain on its own?
When data are passed to the component, I use them in 2 cases: 1) on the HTML view itself, 2) inside the onNgInit().
Data on the HTML view are rendered completely perfect, but inside of onNgInit, my property is not initialized yet. I suspect this is due to the components’ lifecycle (which I don’t know well) and something is not ready yet so I don’t have access to the data.
As a workaround, I could move my logic in the component from onNgInit to ngAfterContentInit on maybe to ngAfterViewInit. But what if my component would need to pass data further to another sub-component…?
Question #2: How to pass data from one component to another so if it is initialized inside onNgInit of the parent then it is also available inside of onNgInit of the child.
Thanks!
EDIT:
Answering the question of https://stackoverflow.com/users/10602679/pankaj-sati in the comment "You mentioned that data on the HTML view is rendered completely perfect, could you show how you have passed the data to another component. Based upon that, a correct lifecycle event could be suggested".
parent.component.ts
export class AppComponent {
@Output()
server: string = '';
...
async ngOnInit(): Promise<void> {
let _this = this;
this.server = AnotherHelper.getServer();
...
parent.component.html
<app-report [server]='server'></app-report>
child.component.ts
export class ReportComponent implements OnInit {
@Input() server!: string;
ngOnInit(): void {
console.log(`Report Server: ${this.server}`); // here 'this.server' is empty
...
report.component.html
<p>Server: {{ server }}</p>
the server value is properly displayed on the report component, but not in the console.
Even though the proper state management can help, I feel like this can also be solved by using proper events (or at least I need to learn events lifecycle to have a better understanding of what’s going on.)
Solution
If this is about the global application state rather then parent-child relationship specifically, then there is a simple solution with a root service. This type of services are auto-injectable due to @Injectable({providedIn:'root'})
decoration and do not require any registration. Consider this:
@Injectable({
providedIn: 'root'
})
export class SimpleStateService {
constructor(private http: HttpClient) {
}
data: ItemModel[] | undefined;
history: CommandModel[] | undefined;
async updateData() {
this.data = await this.http.get<ItemModel[]>('/api/data').toPromise();
}
async updateHistory() {
this.history = await this.http.get<CommandModel[]>('/api/history').toPromise();
}
async revertEvent(commandId: string) {
await this.http.post<CommandModel>(`/api/history/revert/${commandId}`, {}).toPromise();
// ...
}
}
This can be easily consumed by component using constructor injection (as a request for DI).
export class HistoryComponent implements OnInit {
constructor(public state: SimpleStateService) {
}
}
by default it is better to keep injections private
but just because it is some global app state – public
here allows us to bind UI templates right away without any extra code, so we can go ahead with
<table mat-table [dataSource]="state.data" class="mat-elevation-z8">
-- or --
<tr *ngFor="let item of state.items">
<td>{{item.property1}}</td>
<td>{{item.property2}}</td>
</tr>
This is a simple way to start from but keep in mind that such service is often leads to the state change mess especially when more features and more services like this will start interfere in a big projects, also because of mixed responsibility (state and communication). The evolution of this is a state management framework like NGRX & Akita, as been mentioned in another answer.