5

So recently i've learnt about Subjects, and i'm trying to use them in a personal project. I have a service that fetches data from a json file and casts it to an "Article" type. Article is a custom class that holds information on a blog article.

My end goal here is to fetch this array of articles, then when I press a + button it adds a new blank Article to the current list, and the view should represent it by showing the blank article with some default values. This does not persist (save to json) the new blank article, but simply add it to the current list so the view updates and displays it. Saving will come later.

I cannot for the life of me get this to work. All the articles are correctly displaying on my "article list" page but manually pushing a blank one to it doesn't seem to do anything at all.

Here is my service file

@Injectable()
export class ArticleService {

  headers: Headers;

  options: RequestOptions;

  articles: ReplaySubject<Article[]>;

  private url = 'data/articles.json';

  constructor(private http: Http) { 
    this.headers = new Headers({ 'Content-Type': 'application/json' });
    this.options = new RequestOptions({ headers: this.headers });
    this.articles = new ReplaySubject<Article[]>();
  }

  /**
   * fetch a list of articles
   */
  getArticles(): Observable<Article[]> {

    // no articles fetched yet, go get!
    return this.http.get(this.url, this.options)
                    .map(response => <Article[]>response.json())
                    .do(data => this.articles.next(data))
                    .catch(this.handleError);
  }

  /**
   * add a new article to the list
   * @param article 
   */
  addArticle(article: any): void {
    this.getArticles().take(1).subscribe(current => {

      //
      //
      // THIS WORKS. this.articles is UPDATED successfully, but the view doesn't update
      // IT ALSO LOOKS LIKE this.articles may be being reset back to the result of the get()
      // and losing the new blank article.
      //
      //

      current.push(article);
      this.articles.next(current);
    });
  }

  ...
}

and I have a list component updating the list like this:

export class ArticleListComponent implements OnInit {

    articles: Article[];

    public constructor(private articleService: ArticleService) { }

    ngOnInit(): void {
      this.getArticles();
    }

    getArticles(): void {
      this.articleService.getArticles().subscribe(articles => this.articles = articles);
    }
}

and another component that creates the new blank article:

export class CreatorComponent {

    articles: Article[];

    public constructor(private articleService: ArticleService) { }

    /**
     * add a new empty article
     */
    add(): void {
        let article = {};
        article['id'] = 3;
        article['author'] = "Joe Bloggs";
        article['category'] = "Everyday";
        article['date'] = "November 22, 2017"
        article['image'] = "/assets/images/post-thumb-m-1.jpg";
        article['slug'] = "added-test";
        article['title'] = "New Via Add";
        article['content'] = "Minimal content right now, not a lot to do."

        this.articleService.addArticle(article);
    }
}

I can debug, and the this.articles property on the service seems to get updated with the new blank article, but the view doesn't change, and i can't tell for sure but it seems like that article is just lost as soon as soon as its added anyway. Is the observable repeating the http get and scrubbing the articles list clean again?

simon_www
  • 489
  • 2
  • 5
  • 13
  • How are these components related? Where have you added service to providers array? – AT82 Nov 24 '17 at 18:46
  • What has the providers array got to do with the logic problem I have? Also, how they're related is clear above. If you read the code you'll see the service injected to both components. One component adds a blank article, the other lists the articles stored on the service. – simon_www Nov 24 '17 at 19:19
  • It's very much related. Where the service is provided is important for it being a singleton service or not. How these components are related is important too, depending on a `Subject` will work or not. Still doesn't know either answers. Looking at the components I cannot say are they parent-child, siblings or what? – AT82 Nov 24 '17 at 19:21
  • I guess I don't really understand RxJs or Angular2+ yet. Its a singleton service. I think if one component was a child of another I genuinely can't understand how it affects the Subject, when the Subject is held in the service. The components are siblings. – simon_www Nov 24 '17 at 19:32
  • FYI, the nature of the relationship only matters so far as they both need to be descended from the same provider of the service. So if it's module provided, then it's a singleton and they will pretty much always be receiving the same "copy" of the service unless it's reprovided by a component somewhere in the tree. if it is component provided, then all components using that service need to be descended from that provider to get the same "copy". – bryan60 Nov 24 '17 at 20:06

1 Answers1

1

You haven't actually subscribed to the subject you're interested in in your display component. You only subscribe to the http call that populates the subject you're interested in and then terminates. Try it more like this:

private articleSub: Subscription;
ngOnInit(): void {
  this.articleSub = this.articleService.articles.subscribe(articles => this.articles = articles);
  this.articleService.getArticles().subscribe();
}

ngOnDestroy() { //make sure component implements OnDestroy
    this.articleSub.unsubscribe(); // always unsubscribe from persistent observables to avoid memory leaks
}
bryan60
  • 28,215
  • 4
  • 48
  • 65
  • Oh that seems to do the trick. It looks like the GET is run a second time when I click "addArticle()", and of course the response doesn't contain my blank article. I'm guessing I need to stop this second request happening else it will scrub the newly added blank one? – simon_www Nov 24 '17 at 19:28
  • that's because you call the http method in your service when you add the article, you could do the same thing though in your add method where you just do this.articles.take(1).subscribe(current => //add it and call next) – bryan60 Nov 24 '17 at 19:30
  • Bingo! Okay So I tried take and subscribe directly on `this.articles` before, and it yielded no results and I think the subscribe callback wasn't working at all so I ended up re-calling the getArticles() method believing that I wasn't using take and subscribe correctly. :) Thank you. – simon_www Nov 24 '17 at 19:35