1

I have a component that is supposed to retrieve data from a service.

Here is my component:

@Component({
    templateUrl: 'app/dashboard/useraccount/firstname/useraccount-firstname.component.html',
    directives: [ROUTER_DIRECTIVES],
    pipes: [TranslatePipe]
})
export class UserAccountFirstNameComponent implements OnInit {

    currentUserAccount:Object;
    errorMessage:string;

    constructor(private userAccountService:UserAccountService) {
    }

    ngOnInit() {
        this.userAccountService.getCurrentUserAccount()
            .subscribe(
                param=> this.currentUserAccount = param,
                error => this.errorMessage = <any>error
            );
    }

    updateFirstName() {
        this.userAccountService.updateFirstName(this.currentUserAccount);
    }

}

Here is the corresponding service:

@Injectable()
export class UserAccountService {

    constructor(private http:Http) {
    }

    getCurrentUserAccount():Observable<Object> {
        return this.http.get('/api/useraccount')
            .map(this.mapUserAccount)
            .catch(this.handleError);
    }

    updateFirstName(currentUserAccount) {
        this.http.patch('/api/useraccount/firstname/' + currentUserAccount.firstName, null);
    }

    private mapUserAccount(res:Response) {
        console.log(res.json());
        return res.json();
    }

    private handleError(error:any) {
        // In a real world app, we might use a remote logging infrastructure
        let errMsg = error.message || 'Server error';
        console.error(errMsg); // log to console instead
        return Observable.throw(errMsg);
    }

Here is how the provider for the UserAccountService:

bootstrap(MainComponent, [ROUTER_PROVIDERS,
    HTTP_PROVIDERS,
    TRANSLATE_PROVIDERS,
    SessionService,
    UserAccountService,
    TranslateService,
    provide(RequestOptions, {useClass: ApplicationRequestOptions}),
    provide(LocationStrategy, { useClass: HashLocationStrategy }),
    provide(TranslateLoader, {
        useFactory: (http:Http) => new TranslateStaticLoader(http, 'assets/i18n', '.json'),
        deps: [Http]
    })]);

Here is the relevant part from the template:

<input type="text"
       ngControl="firstName"
       #firstName="ngForm"
       required
       minlength="2"
       maxlength="35"
       pattern_="FIRST_NAME_PATTERN"
       [(ngModel)]="currentUserAccount.firstName"
       placeholder="{{'FIRST_NAME_FORM.NEW_FIRST_NAME'| translate }}"
       class="form-control"/>

The issue is that by the time the template is rendered, currentUserAccount is still undefined in ngModel...

Can anyone please help?

P.S. I am puzzled as my use case (having a component calling a service method that uses http) is very similar to the angular sample provided here: http://plnkr.co/edit/DRoadzrAketh3g0dskpO?p=preview

balteo
  • 23,602
  • 63
  • 219
  • 412
  • It's hard to tell, but it seems like you are not calling `updateFirstName` anytime after instantiating. Also, it needs to be handled asynchronously. The same way you did with `ngOnInit`. If that isn't it let, me know. – ZenStein May 14 '16 at 14:21
  • + `updateFirstName` in your service is not return anything. – ZenStein May 14 '16 at 14:29
  • `updateFirstName` is irrelevant here. It is `userAccountService.getCurrentUserAccount` that is relevant. – balteo May 14 '16 at 14:48
  • I should probably have not included updateFirstName here in the first place. – balteo May 14 '16 at 14:48

1 Answers1

1

Angular resolves bindings already before ngOnInit(). You you just invoke the async call to the server to fetch the data, which will arrive eventually (and execute the callback you passed to this.userAccountService.getCurrentUserAccount().subscribe(...)

To avoid an error when currentUserAccount is still null when Angular binds the view you can use the Elvis or safe navigation operator ?. for parent-to-view binding [] but not for event-to-parent binding () this isn't supported (there are discussions to add support for this) You can use this more verbose style though:

<input type="text"
       ngControl="firstName"
       #firstName="ngForm"
       required
       minlength="2"
       maxlength="35"
       pattern_="FIRST_NAME_PATTERN"
       [ngModel]="currentUserAccount?.firstName"
       (ngModelChange)="currentUserAccount ? currentUserAccount.firstName = $event : null"
       placeholder="{{'FIRST_NAME_FORM.NEW_FIRST_NAME'| translate }}"
       class="form-control"/>
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thanks a lot Günter. I wonder if there is a way to have resolves as with the ng 1.x router? – balteo May 14 '16 at 14:55
  • Sorry, I don't know Ng1 well, especially not the router. What does it do? – Günter Zöchbauer May 14 '16 at 14:56
  • It resolves promises (or observables) before the route is rendered. Maybe something like implementing `OnActivate` could help... – balteo May 14 '16 at 15:02
  • See: https://angular.io/docs/ts/latest/api/router/OnActivate-interface.html – balteo May 14 '16 at 15:03
  • I see. Thanks for the info. `OnActivate` is supposed to allow that, but there is an open issue that it doesn't wait for the promise to resolve AFAIK. A simple workaround is to add `*ngIf="currentUserAccout"` to the outermost element in your view. – Günter Zöchbauer May 14 '16 at 15:05
  • But `*ngIf`will just **not** display the view? Or wait for the observable to become available? – balteo May 14 '16 at 15:07
  • At first the view is not rendered. When then `currentUserAccount` changes from `undefined` (or `null`) to some value, the view is rendered. – Günter Zöchbauer May 14 '16 at 15:08
  • Wow! I was not aware of this feature of `*ngIf`. – balteo May 14 '16 at 15:16
  • That's just Angulars change detection and JavaScripts truthiness rules. You can also write `*ngIf="currentUserAccount !== undefined && currentUserAccount !== null"` – Günter Zöchbauer May 14 '16 at 15:18