Search code examples
angularangular2-routingangular2-services

Angular2 Observable not rendering until data changed


I am new to Angular2, so hopefully I have an easy problem to solve :)

I am trying to share data (a list of objects, locally stored in memory by service) across multiple views (that are separate routes) using an observable service. The problem is that when I change route (i.e. through a link), the shared data does not render until I "add" a new item to the list (i.e. in current route). I've seen many similar issues on stack overflow but none of the solutions have quite worked.

I don't know if the error is in my routing, my ngInit function (i.e. rendering before data is complete?) or in the way I've defined the service/component themselves. Here are some code snippets to illustrate:

Routing:

const appRoutes: Routes = [
  {
    path: '',
    redirectTo: '/running-order',
    pathMatch: 'full'
  },
  {
    path: 'running-order',
    component: RunningOrderComponent
  },
  {
    path: 'enquiry',
    component: EnquiryComponent
  },
  {
    path: 'config',
    component: ConfigComponent
  }
];

export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);

Service:

@Injectable()
export class ChaptersService{

    //Observable Source
    private chaptersSource:Subject<BaseChapter[]>;

    //Observable Stream
    chapters$: Observable<BaseChapter[]>;

    //DataStore
    private dataStore:{ chapters: BaseChapter[] };

    constructor() {
        console.log('chapter service instantiated');
        this.chaptersSource = new Subject<BaseChapter[]>();
        this.chapters$ = this.chaptersSource.asObservable();
        this.dataStore = { chapters: [] };      
    }

    loadAll() {
        this.chaptersSource.next(this.dataStore.chapters);
    }

    addChapter(data:BaseChapter){
        this.dataStore.chapters.push(data);
        this.chaptersSource.next(this.dataStore.chapters);
    }
}   

Component (Routed):

@Component({
    selector: 'myenquiry',
    templateUrl: 'enquiry.component.html',
    styleUrls: ['enquiry.component.css']
})

export class EnquiryComponent implements OnInit{
    config : Config;
    title = 'Enquiry Angular App';
    selectedChapter: BaseChapter;

    chapters$: Observable<BaseChapter[]>;

    constructor(private chaptersService:ChaptersService){}

    ngOnInit():void {
        this.chaptersService.loadAll();
        this.chapters$ = this.chaptersService.chapters$;
        console.log('enquiry component initialised...');
    }

    onSelect(chapter: BaseChapter): void {
      this.selectedChapter = chapter;
    }

    addChapter():void {
        var chapter:Chapter = new Chapter(0);
        chapter.initChapter("untitled",4);
        this.chaptersService.addChapter(chapter);
        this.selectedChapter = chapter;
    }
}

Component Template:

<h2>List of Chapters</h2>
<ul class="chapters">
    <li *ngFor="let chapter of chapters$ | async" [class.selected]="chapter === selectedChapter" (click)="onSelect(chapter)" >
        <span class="idStyle">{{chapter.id}}</span> {{chapter.name}}
    </li>
</ul>
<chapter [chapterData]="selectedChapter"></chapter>
<button (click)="addChapter()">Add Chapter</button>

thanks, Phil.


Solution

  • Change

    private chaptersSource:Subject<BaseChapter[]>; 
    

    to be

    private chaptersSource:ReplaySubject<BaseChapter[]>;
    

    that way if the observable gets a value BEFORE your component has bound it will get the last value pushed still.