0

Trying to update a HTML element between components use an angular service doesn't seem to work for me.

This is not a duplication of How do I return the response from an asynchronous call?.

Full project: https://github.com/flamusdiu/micro-blog/tree/dev

I have this service:

import { ElementRef, Injectable } from '@angular/core';
import { MdSidenav } from '@angular/material';
import { AsyncSubject } from 'rxjs/AsyncSubject ';

import { Article } from '../models/article';

@Injectable ()
export class InterModuleService {
    private _article: AsyncSubject <Article> = new AsyncSubject();      
    public sidenav: MdSidenav;
    public sidenavToc: ElementRef;
}

I have placed my observable here. The observable is filled through an Angular Resolver for my route. The component is setup as:

import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute }   from '@angular/router';

import { Article } from '../../models/article';
import { InterModuleService } from '../../service/inter-module.service';

@Component({
  selector: 'app-article-detail',
  templateUrl: './article-detail.component.html',
  styleUrls: ['./article-detail.component.css']
})
export class ArticleDetailComponent implements OnInit {

  private article: Article;

  constructor( private route: ActivatedRoute, private interModuleService: InterModuleService ) { }

  ngOnInit(): void {
    this.route.data
        .subscribe((data: { article: Article } ) => {
            this.interModuleService.article.next(data.article);
        });

    this.interModuleService.article
        .subscribe((data) => {
            this.article = data;
            this.interModuleService.sidenavToc.nativeElement['innerHTML'] = data.toc;
        });
  }
}

I get this is some sort of Async problem. I get the article data which updates the view just find. However, any property of the article object is always "undefined" or "null" when read at any point which makes me thing my method of trying to do this is not completely right.


If I add some console.logs like this:

this.interModuleService.article.take(1)
        .subscribe((data) => {
            this.article = data;
            console.log(this.article);
            console.log(this.article.toc);
            this.interModuleService.sidenavToc.nativeElement['innerHTML'] = data.toc;
        });

I get the follow screenshot (ignore the HTML formatting issues):

screencap of issue


Just for reference: article.toc is shorthand for article.attachments['toc']['data']. However, referencing this directly yields the same result as above. article.attachments['toc'] has data but the property data is undefined when called.


Changed from BehaviorSubject to AsyncSubject since I only really care about the last one emitted. Probably should not expose the Subject but didn't see a good way to not do that. Though data.toc is still undefined. =(

flamusdiu
  • 1,722
  • 2
  • 14
  • 31

1 Answers1

0

I had to move the whole async process of getting the attachments closer to where I needed it for some reason. I am still unsure of the reason for doing this other then something to do with the way Async processes resolve and expecting a value that might come at any point needs to be accounted for.

Here is the working code:

inter-module.service.ts:

import { ElementRef, Injectable } from '@angular/core';
import { MdSidenav } from '@angular/material';
import { AsyncSubject } from 'rxjs/AsyncSubject';

import { Article } from '../models/article';

@Injectable ()
export class InterModuleService {
    public article: AsyncSubject<Article> = new AsyncSubject();

    public sidenav: MdSidenav;
    public sidenavToc: ElementRef;  
}

article-detail.component.ts

import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute }   from '@angular/router';

import { Article } from '../../models/article';

import { ArticleStore } from '../../state/ArticleStore';
import { InterModuleService } from '../../service/inter-module.service';

@Component({
  selector: 'app-article-detail',
  templateUrl: './article-detail.component.html',
  styleUrls: ['./article-detail.component.css']
})
export class ArticleDetailComponent implements OnInit {

  private article: Article;

  constructor( private route: ActivatedRoute, 
               private articleStore: ArticleStore,
               private interModuleService: InterModuleService ) { }

  ngOnInit(): void {
    this.interModuleService.article
        .subscribe((data) => {
            this.article = data;

            Promise.all(Object.keys(this.article['attachments']).map((at) => {
                return this.articleStore.getAttachment(this.article['id'],at).then ((res) => {
                    this.article.attachments[at]['data'] = res.toString();
                })
            })).then(()=> {
                this.interModuleService.sidenavToc.nativeElement['innerHTML'] = this.article.attachments['toc'].data;
            });

        });
    this.route.data
        .subscribe((data: { article: Article } ) => {
            this.interModuleService.article.next(data.article);
            this.interModuleService.article.complete();
        });
  }
}

article-detail.resolver.ts

import { Injectable } from '@angular/core';
import { Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router';
import { Article } from '../../models/article';
import { ArticleStore } from '../../state/ArticleStore';


@Injectable()
export class ArticleDetailResolver implements Resolve<Article> {

  constructor(private articleStore: ArticleStore, private router: Router) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Article> {

    let id = route.params['id'];

    return this.articleStore.getArticle(id).then(article => {
      if (article) {
        return new Article(article);

      } else { // id not found
        this.router.navigate(['/articles']);
        return null;
      }
    });
  }
}
flamusdiu
  • 1,722
  • 2
  • 14
  • 31