3
\$\begingroup\$

There are different methods to get data from server in Angular application:

  1. Get Observable from the service and subscribe to it at component
  2. Create Subject at the service and subscribe to the Subject at component

Both of this methods work for me but I can't understand which should I use.

First method. Get Observable from the service and subscribe to it at component:

article.service.ts

import { Injectable } from '@angular/core';
import { Article } from '../models/article';
import { AngularFirestore } from '@angular/fire/firestore';
import { map, take } from 'rxjs/operators';
import { Observable, Subject } from 'rxjs';

@Injectable({
  providedIn: "root"
})
export class ArticleService {
  public articlesChanged: Subject<Article[]> = new Subject<Article[]>();
  articles: Article[];

  constructor(private db: AngularFirestore) {}

  get() {
    return this.db.collection('articles').valueChanges({ idField: 'id' });
  }
}

home.component.ts

import { Component, OnInit } from '@angular/core';
import { ArticleService } from 'src/app/services/article.service';
import { Observable, Subscription } from 'rxjs';
import { Article } from 'src/app/models/article';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})

export class HomeComponent implements OnInit {
  articles: Article[];

  constructor(private articlesService: ArticleService) { }

  ngOnInit() {
    this.articlesService.get().subscribe(articles => this.articles = articles as Article[]);
  }
}

Second method. Create Subject at the service and subscribe to the Subject at component:

article.service.ts

import { Injectable } from '@angular/core';
import { Article } from '../models/article';
import { AngularFirestore } from '@angular/fire/firestore';
import { map, take } from 'rxjs/operators';
import { Observable, Subject } from 'rxjs';

@Injectable({
  providedIn: "root"
})
export class ArticleService {
  public articlesChanged: Subject<Article[]> = new Subject<Article[]>();
  articles: Article[];

  constructor(private db: AngularFirestore) {}

  get(): void {
    this.db
      .collection('articles')
      .valueChanges({ idField: 'id' }).subscribe(articles => {
        this.articles = articles as Article[];
        this.articlesChanged.next(this.articles);
      });
  }
}

home.component.ts

import { Component, OnInit, OnDestroy } from '@angular/core';
import { ArticleService } from 'src/app/services/article.service';
import { Observable, Subscription } from 'rxjs';
import { Article } from 'src/app/models/article';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})

export class HomeComponent implements OnInit, OnDestroy {
  articlesSubscription: Subscription;
  articles: Article[];

  constructor(private articlesService: ArticleService) { }

  ngOnInit() {
    this.articlesSubscription = this.articlesService.articlesChanged.subscribe(articles => this.articles = articles);
    this.articlesService.get();
  }

  ngOnDestroy(): void {
    this.articlesSubscription.unsubscribe();
  }
}

Is there a best practice which I should use?

\$\endgroup\$
1
\$\begingroup\$

I think at the end it depends on the use case.
For a simple loading of data, i would use the approach of getting the Observable directly. When the backendcall is not anymore the single source of changes (for example when i could change the data in the frontend and persist it later), or when i want to "store" the data, so that i do not have to call the backend for each request, then i would start thinking about providing an extra observable.

But i would not make the "storage" public, because then others would be able to emit values on it. I would make it private and provide a method that gives access to the data.

// ...
private articles$ : BehaviorSubject<Article[]>([]);

public getArticles(): Observable<Article[]> {
   return this.articles$.pipe(
     distinctUntilChanged();
   )
}

public loadArticles(): void {
  this.db.collection('articles').valueChanges({ idField: 'id' })
  .subscribe( 
    (articles: Article[]) => this.articles.next(articles)
  );
}

In my experience i worked mostly with a direct connected backend call. Perhaps with a pipe to map the backend data format into my frontend data format, but thats it.
The other approach i use mostly when i want to implement a central data source.

By the way, i have the tendency to type the return types of my methods, so that the transpiler can provide me some support. :-)

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.