2

I have a couple of navigation buttons in an Angular app. What I'm trying to do is remove "Prev" on the first page and "Next" on the last page.

All I want to do is set two booleans that say whether it's the first or last page.

Trouble is I haven't managed to get my head around these subscribers and behaviours.

I can create a function that calculates the last page from totalItems and itemsPerPage so please don't worry about that, I just have no idea how to call a function from this subscribe.

If someone can please show me how to write a callback or whatever else is needed to do this I will be eternally grateful.

Component.html:

<div class="pagination-panel clearfix">
    <button (click)="previousPage()" *ngIf="firstPage != true">Prev</button>
    <button (click)="nextPage()" *ngIf="lastPage != true">Next</button>
</div>

<!-- How the data is used, if that helps  -->
<h2 class="spray-header">Presenters</h2>
<article *ngFor="let presenter of (presenters$ | async)?.data" class="presenter-item">
    <div class="presenters-header">
        <h3 class="presenters-title margin-none">
            <a [routerLink]="['/presenters', presenter.id]">{{presenter.name | uppercase}}</a>
        </h3>
        <p class="presenters-details">{{presenter.details}}</p>
    </div>
    <img src="{{presenter.small_thumb}}" class="presenters-profile-picture profile-picture">
</article>

Component.ts

import {Component, OnInit, Inject} from '@angular/core';
import {Http} from "@angular/http";
import "rxjs/add/operator/map";
import {BehaviorSubject} from "rxjs/BehaviorSubject";
import {ActivatedRoute, Router} from "@angular/router";

export class PresentersComponent implements OnInit {
    firstPage = true;
    lastPage = false;
    presenters$ = new BehaviorSubject([
        {
            data: {
                id: 0,
                name: "Loading...",
                details: "Loading...",
                small_thumb: "",
                radio_shows: []
            },
            currentPage: 1,
            itemsPerPage: 0,
            totalItems: 0
        }
    ]);

    constructor(@Inject('api') private api,
                private colorService: ColorService,
                private http: Http,
                private route: ActivatedRoute,
                private router: Router) {
        route.params
            .map((p: any) => p.page)
            .switchMap(page => http.get(api + '/presenters?page=' + page))
            .map(res => res.json())
            .subscribe(this.presenters$);
    }
}

Update 1

Could something like this work?

console.log(x); seems to give me back the right data, but now none of my (presenters$ | async)?.data stuff is working.

        .subscribe(
            function (x) {
                console.log(x);
                this.presenters$ = x;
            },
            null,
            function () {
                console.log("set the values here");
            }
        );

Update 2

The above breaks because this refers to the function call (duh!), but I get errors when attempting to use the fat arrow, anyone know what I'm doing wrong here?

.subscribe(
    x => this.presenters$ = x,
    null,
    function () {
        console.log("set the values here");
   }
);

The above doesn't throw and error but still the async is not working.

The below throws this error: "Error: InvalidPipeArgument: '[object Object]' for pipe 'AsyncPipe'"

.subscribe(res => {
    this.presenters$ = res;
    // Any other lines here.
});

According to the link below it looks like I just need to remove the async pipe, will give it a shot later. Angular 4: InvalidPipeArgument: '[object Object]' for pipe 'AsyncPipe'

Update 3

By removing the async pipe from the data call in the template (presenters$ | async)?.data" I got over the error in the last update and managed to get the functionality I'm after working in the dev environment.

// component.html change
(presenters$)?.data

// latest subscribe that I have working, sort of
.subscribe(res => {
    this.presenters$ = res
    // got extras lines of code working here
});

The problem now is that won't compile for production. When I try to compile I get this error:

ERROR in ng:///var/www/html/io->radio/src/app/presenters/views/presenters.component.html (2,1): Property >'data' does not exist on type 'BehaviorSubject<{ data: { id: number; >name: string; details: string; small_thumb: string; radio_s...'.

Update 4

I'm beginning to think that I'm being a total spaz here, and that BehaviorSubject is not what I ought to use. All I'm trying to do is call data from my API and map it to an object, if someone can tell me what I'm doing wrong here it would be a great help.

I think that my issues are due to inappropriate usage of the BehaviorSubject and a lack of understanding of the async pipe.

My next step is to try out the very sensible suggestion from @DeborrahK and create a service for retrieving the data.

OrderAndChaos
  • 3,547
  • 2
  • 30
  • 57
  • Is a BehaviorSubject really needed here? Also, consider encapsulating the http request into a service and moving this code to the ngOnInit instead of the constructor. – DeborahK Aug 07 '17 at 22:06
  • Mate I have no idea, I followed a load of Egghead.io tutorials, seemed to make sense at the time but now I'm a bit lost. I think it abstracted beyond my understanding too fast and now this is where I'm at... – OrderAndChaos Aug 07 '17 at 22:09
  • I'll add a bit of how I'm using the data. – OrderAndChaos Aug 07 '17 at 22:10
  • The problem with the above code is that since you used the keyword `function` instead of the arrow syntax (=>), the `this` in `this.presenters$` is no longer referencing the class property and is instead defining a variable local to the defined function. Try changing the `function(x)` to `x =>` and see if that helps. – DeborahK Aug 07 '17 at 22:56
  • Ha, thanks, of course it does! That makes total sense now. – OrderAndChaos Aug 08 '17 at 06:27

1 Answers1

1

The trick is to add the curly braces around multiple lines of code. I have an example below.

.subscribe(res => {
                this.presenters$ = res;
                // Any other lines here.
            });

But you may want to consider something like this instead:

ngOnInit(): void {
    this.route.params.subscribe(
       params => {
            const id = +params['id'];
            this.getMovie(id);
            // I could have more lines of code here.
        }
    );
}

This bit of code is executed when the component is initialized. It watches for changes to your parameters and gets different data as the parameters change. You can dramatically simplify the above code if the parameters don't change.

getMovie(id: number): void {
    this.movieService.getMovie(id)
        .subscribe(
              movie => {
                this.onMovieRetrieved(movie);
                // I could have more lines of code here
              },
              error => {
                 this.errorMessage = <any>error;
                 // I could have more lines of code here
              }
         );
}

This code calls a service to retrieve the data. The subscribe() is here. The http.get is within the service.

This uses movies instead of the data you have, but the concepts and structure would be the same.

I have a complete example here: https://github.com/DeborahK/MovieHunter

DeborahK
  • 57,520
  • 12
  • 104
  • 129
  • I've seen this bottom section structure around a lot (one of the reasons I've not been able to map it to my problem). Will give the curly brackets a go. – OrderAndChaos Aug 07 '17 at 22:15
  • I kind of get that it's `.subscribe(assignData, handleError, handleCompletion)` or something along those lines? – OrderAndChaos Aug 07 '17 at 22:16
  • Or you could adjust your code to match this very common pattern. Following common patterns makes it easier for us to help. – DeborahK Aug 07 '17 at 22:16
  • Yes, but each of those can be a set of code. I'll modify my subscribe in my example above to demonstrate. – DeborahK Aug 07 '17 at 22:17
  • And I put the curly braces in the wrong place the first time I typed them in. Sorry for any confusion. – DeborahK Aug 07 '17 at 22:21
  • What were you trying to do with those first curly braces? I'd like to work out how to do it from where I am, there are a ton of other pages that use this pattern in my app, and generally it works great. If it's possible I'd like to understand how to apply a callback to my existing code. I like doing things the hard way. – OrderAndChaos Aug 07 '17 at 22:30
  • You have it in your update. I'll update my answer with it. – DeborahK Aug 07 '17 at 22:36
  • Sorry, I hadn't finished writing the update, was still playing with some ideas. I'm not sure why but that breaks the async stuff. – OrderAndChaos Aug 07 '17 at 22:43
  • I'll take a look tomorrow evening after work, to see if it's clearer to me then. Thanks for all your help I think it's making more sense, I'm too tired to know at this point. – OrderAndChaos Aug 07 '17 at 22:47
  • With that curly bracket example I get this error "ERROR Error: InvalidPipeArgument: '[object Object]' for pipe 'AsyncPipe'" – OrderAndChaos Aug 08 '17 at 06:41
  • I've updated my question as it didn't accurately reflect to content of the post, I think that you have provided a much better way to retrieve data from the API which I will implement, but I really want to know what I'm doing wrong, I hope the the updates will make that clearer and easier to answer. – OrderAndChaos Aug 09 '17 at 06:33