Search code examples
angularangular2-routingangular2-services

How to push data into array in Angular 2 when using similar route


So far, I haven't found a simple example of how to push data into an array in Angular 2. In AngularJs it was easy (example), but I'm struggling for it in Angular 2, maybe because I'm using router and I don't know how to configure it (I was following the angular heroes example).

What I want to do, the whole solution:

app.module.ts:

import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { LocationStrategy, HashLocationStrategy } from '@angular/common';
import { HttpModule } from '@angular/http';
import { ProductsService } from '../services/ProductsService';


import { AppComponent } from "./components/app";
import { Products } from "./components/products";

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule,
        RouterModule.forRoot([
            {
                path: 'products/:class_id/:type_id',
                component: Products
            }
        ], { useHash: true })
    ],
    exports: [RouterModule],
    declarations: [
        AppComponent
        Products
    ],
    providers: [
        ProductsService
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

AppComponent.ts

import { Component} from "@angular/core";

@Component({
    selector: "my-app",
    template: `<div>
                <a [routerLink]="['/products', 1, 1]">Products-1</a>
                <a [routerLink]="['/products', 2, 2]">Products-2</a>
               </div>`
})
export class AppComponent{}

ProductsService.ts

import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class ProductsService {

    private headers = new Headers({ 'Content-Type': 'application/json' });

    constructor(private http: Http) { }

    getProducts(class_id: string, type_id: string, index: number, numberOfObjectsPerPage: number): Promise<any> {
        return this.http.get('Home/GetProducts?pageIndex=' + index +'&classId=' + class_id + '&typeId=' + type_id)
            .toPromise()
            .then(response => response.json() as any)
            .catch(this.handleError);
    }

    private handleError(error: any): Promise<any> {
        console.error('An error occurred', error); // for demo purposes only
        return Promise.reject(error.message || error);
    }
}

Product.ts

import 'rxjs/add/operator/switchMap';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import * as $ from 'jquery';

import { ProductsService } from '../../services/ProductsService';
import { Product } from '../../common/models/Product';

@Component({
    selector: 'products',
    templateUrl: 'Products.html',
})
export class Products implements OnInit {
    products: Array<Product> = [];
    numberOfObjectsPerPage: number = 10;
    index: number = 0;
    constructor(
        private productsService: ProductsService,
        private route: ActivatedRoute
    ) {}

    ngOnInit(): void {
        this.loadProducts();
    }

    loadProducts():void{
        this.route.paramMap
            .switchMap((params: ParamMap) =>
                this.productsService.getProducts(params.get('class_id'), params.get('type_id'), this.index, this.numberOfObjectsPerPage))
            .subscribe(products => {
                this.products = products;
        });
    }

    showMore():void{
        this.index++;
        this.loadProducts();
    }
}

Products.html:

<div class="product" *ngFor="let product of products;">
  {{ product.name }}
</div>
<button (click)="showMore()">Show more</button>

So, what is the problem here: if I go to Products-1 I get 10 products, which is obvious, but if I press Show more then the first 10 products are removed and another 10 are shown - which again is obvious, so to avoid this and keep the first 10 and load 10 more, I replaced Product.ts -> this.products = products; to:

 for (let i = 0; i < products.length; i++) {
  this.products.push(products[i]);
 }

Another problem appears now: when I go to Product-2, the products from Products-1 are shown along with the products of Product-2, so to avoid this, I added this two lines into the Product.ts constructor:

constructor(private productsService: ProductsService, private route: ActivatedRoute) {
        route.paramMap.subscribe(params => this.products = []);
        route.paramMap.subscribe(params => this.index = 0);
}

Now, everything works just fine except: when I go from Products-1 to Products-2 and load more products, and after that return to Products-1, I can see in my network tab that multiple requests of same type are being send to my server.

So my questions are:

  1. Is there a better way to push data when using routing, and avoid to reset the parameters in the constructor ?
  2. If the pushing into the array is okay, and if it is okay to reset the parameters in the constructor, then, how to avoid multiple requests when going from one route to another ?

Solution

  • There are several things that I would add/change in product.ts:

    1. Add those properties under your index: number = 0; property:

      class_id: string;  
      type_id: string;
      
    2. Remove route.paramMap.subscribe from the constructor

    3. ngOnInit, should look like this:

      ngOnInit(): void {
        this.route.paramMap.subscribe(params => {
          this.products = [];
          this.index = 0;
          this.type_id = params.get('type_id');
          this.class_id = params.get('class_id');
          this.loadProducts();
        });   
      }
      
    4. And finally, loadProducts():

      loadProducts(): void {
        this.productsService.getProducts(this.class_id, this.type_id, this.index, this.numberOfObjectsPerPage).then(
          productArray => {
               this.products = this.products.concat(productArray);
          })
      }