Service does not work well with *ngIf in Angular2

905 Views Asked by At

EDIT: Hmm, I should be more clear. This is an angular2-meteor project. Because meteor is reactive, so maybe has other ways. But @lodewijk-bogaards is an good solution for pure Angular2 project.

I am using Angular 2 (TypeScript).

I try to use ChatService service to pass value "12345" from App to ChatDetailsComponent.

If the boolean showChatDetails is true, it will show "12345" the first time I click the Show button, which works well.

The problem is, if the boolean showChatDetails is false, it will show "12345" after the second time I click the Show button. I don't know why it works the second time I click it, which should be first time too.

(Please don't switch to [hidden], because I need *ngIf here.)

// app.ts

import {Component, View} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {ChatService} from './chat.service';
import {ChatDetailsComponent} from './chat-details.component';

@Component({
    selector: 'app'
})
@View({
    directives: [ChatDetailsComponent],
    template: `
        <button (click)="showButton()">Show</button>
        <chat-details-component *ngIf="showChatDetails"></chat-details-component>
    `
})
class App {
    // Here if it is true, it works well. If it is false, the problem comes.
    showChatDetails:boolean = false;  

    constructor(private _chatService: ChatService) {}

    showButton() {
        this._chatService.setChatId("12345");
        this.showChatDetails = true;
    }
}

bootstrap(App, [ChatService]);

// chat-details.component.ts

import {Component, View} from 'angular2/core';
import {ChatService} from './chat.service';

@Component({
    selector: 'chat-details-component'
})
@View({
    template: `
        <div>ID: {{chatId}}</div>
    `
})
export class ChatDetailsComponent {
    chatId:string;

    constructor(private _chatService: ChatService){}

    ngOnInit() {
        this._chatService.getChatId().subscribe(id => this.chatId = id);
    }
}

// chat.service.ts

import {EventEmitter} from 'angular2/core';

export class ChatService {
    chatId: EventEmitter<string> = new EventEmitter();

    setChatId(id) {
        this.chatId.emit(id);
    }

    getChatId() {
        return this.chatId;
    }
}
2

There are 2 best solutions below

4
On BEST ANSWER

Looks like a race condition to me. If the ChatDetailsComponent subscribes to the ChatService after the chatId has been emitted it will not receive it. This is very easily possible, since Angular will schedule the creation of the component at the same time as Rx schedules the emit of the event.

I can come up with multiple solutions, but I would suggest that you look into using a ReplaySubject instead of an EventEmitter.

0
On

I'm not sure if your aim is to accomplish the task the very way you stated or if you're flexible in how it's done.

I think it's much less error prone to make chatId an @Input() of ChatDetailsComponent and have App create a new instance of the component whenever chatId changes.

The containing component has to know when an instance of ChatDetailsComponent needs to be created anyway. Having it pass the needed chatId irhgt away seems to be a better approach to me.