Search code examples
meteortypescriptangularangular-meteorangular2-meteor

Service does not work well with *ngIf in Angular2


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;
    }
}

Solution

  • 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.