In my Angular application I have an API which returns the settings of the footer and the header of the web-site. However I want to call that API only once and use the data for both the header and the footer. There's no direct parent of the header and footer components so I can't just call it from the parent component and pass the data to the child components. As I understand, the correct way of doing this is a service. I've tried to do it as it was described here but this doesn't seem to work. So, my idea is that I need to have a dedicated place which will call the HTTP API, get the data and then the other components can just use that service (inject it) to get the data, all in one place.
Any clues how this can be implemented or what's wrong with the implementation in the topic referenced?
The code which doesn't work looks like this:
import { Observable } from 'rxjs/Observable';
@Injectable()
export class PersonService {
// The person subject
personStream: ReplaySubject<Person> = new ReplaySubject();
// The person observable
person$(): Observable<Person> {
return this.personStream.asObservable();
}
// Get person from DB and add to stream
getDataFromDB() {
this.http.get(url).subscribe(response => {
this.personStream.next(response.person);
});
}
}
@Component({...})
export class MyComponent implements OnInit {
person: Person;
constructor(private personService: PersonService) {}
ngOnInit() {
// Subscribe to person observable. Any time person data changes, this will be called.
this.personService.person$().subscribe(person => this.person = person);
// If you return the `this.http.get(url)` from `getDataFromDB`, you can just do this instead...
this.personService.getDataFromDB().subscribe(person => this.person = person);
}
}
Thanks!
CodePudding user response:
Either you can create Angular Service or you can use any state management library (NgRx) to store data and then share it in multiple pages.
Please attach your code snippet (better upload your code to stackblitz) here so that issue can be resolved.
CodePudding user response:
The example below is one way of achieving what you're looking for.
Consider the following generalized object that may be returned by your settings API:
{
// Settings for the header.
"header": { }
// Settings for the footer.
"footer": { }
}
The Angular service to retrieve these settings may look as follows:
import { shareReplay } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class SettingsService {
/**
* Observable that emits app settings.
*/
readonly app$ = this.httpClient.get('url/to/settings/api').pipe(
// The RxJs shareReplay operator ensures that the backend API is
// only called once, and any subsequent subscription to app$ will
// receive the value cached by shareReplay.
shareReplay(1)
);
constructor(private httpClient: HttpClient) { }
}
The $ suffix denotes that the property is an Observable.
The header component can then extract whatever it needs from the settings using the RxJs map operator.
import { map} from 'rxjs/operators';
@Component({ /* ... */ })
export class HeaderComponent {
/**
* Observable that emits the header settings.
*/
settings$ = this.settingsService.app$.pipe(
// Extract the header settings from the app settings.
map(app => app.header)
);
constructor(private settingsService: SettingsService) { }
}
The component template can subscribe to the settings$
Observable using the Angular async
pipe:
<ng-container *ngIf="(settings$ | async) as settings">
{{ settings.whatever }}
</ng-container>
Do the same for the footer or any other component that needs information from the SettingsService.
