简体   繁体   中英

Angular - get synchronously from Promise

I want to print history of products. I have an id of product in ActivatedRoute.params. In ngOnInit method I have to get all history of product and assign to variable. Then I want to map product to productHistory, because I want to have last version with history toghether. But the problem is with getting history. Method to getting history return Promise and I cannot get length of productsHistory when I use this property and I get undefined. How can I get this property after loading from service?

I want to execute method after execution getHistory().

My code:

ProductService.ts:

import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';

import 'rxjs/add/operator/toPromise';

// rest imports

@Injectable()
export class ProductService {

    // URL to web api
    private projectsUrl = 'http://localhost:8080/products';

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

    constructor(private http: Http) {}

    getHistory(id: number): Promise<ProductHistory[]> {
        const url = `${this.projectsUrl}/projectId/${id}`;
        return this.http.get(url)
            .toPromise()
            .then(response => response.json() as ProductHistory[])
            .catch(this.handleError);
    }

    handleError() {
        //...
        // implementation is irrelevant
    }
}

ProductHistoryComponent.ts:

import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';

import { ProductService } from './product.service';

import { ProductHistory } from './product-history';
import { Product } from './model/product';

import 'rxjs/add/operator/switchMap';

@Component({
    selector: 'product-history',
    templateUrl: './product-history.component.html',
    styleUrls: [ './product-history.component.css' ]
})
export class ProductHistoryComponent implements OnInit {

    auditProducts: ProductHistory[] = new Array<ProductHistory[]>();    
    selectedProduct: ProductHistory;

    constructor(
        private route: ActivatedRoute,
        private location: Location,
        private productService: ProductService
    ) {}

    ngOnInit(): void {
        let id: number = this.route.snapshot.params['id'];

        this.productService.getHistory(id)
            .then(history => this.historyProducts = history);

        this.productService.getProduct(id)
            .then(product => {
                let lastVersion: ProductHistory = this.createLastVersion(product);
                this.auditProducts.push(lastVersion);
            });
    }

    onSelect(ProductHistory: ProductHistory): void {
        this.selectedProduct = ProductHistory;
        this.compare(this.selectedProduct);
    }

    goBack(): void {
        this.location.back();
    }

    compare(history: ProductHistory): void {
        let previous: ProductHistory;
        if (history.changeNumber != null && history.changeNumber > 1) {
            previous = this.historyProducts[history.changeNumber - 2];
            if (typeof previous != 'undefined') {
                this.setPreviousDiffsFalse(previous);
                if (previous.name !== history.name) {
                    history.nameDiff = true;
                }
                if (previous.price !== history.price) {
                    history.priceDiff = true;
                }
            }
        }
    }

    createLastVersion(product: Product): ProductHistory {
        let lastVersionProduct: ProductHistory = new ProductHistory();
        lastVersionProduct.id = this.historyProducts.length + 1;
        lastVersionProduct.name = product.name;
        lastVersionProduct.price = product.price;
        lastVersionProduct.changeNumber = this.historyProducts[this.historyProducts.length - 1].changeNumber + 1;
        return lastVersionProduct;
    }

    setPreviousDiffsFalse(previous: ProductHistory): void {
        previous.nameDiff = false;
        previous.priceDiff = false;
    }

}

I would suggest using observables instead of promises ... but to answer your question, you just need to perform the second request after the first is received. Something like this:

ngOnInit(): void {
    let id: number = this.route.snapshot.params['id'];

    this.productService.getHistory(id)
        .then(history => {
               this.historyProducts = history);

               this.productService.getProduct(id)
                     .then(product => {
                         let lastVersion: ProductHistory = this.createLastVersion(product);
                         this.auditProducts.push(lastVersion);
        });
     }
}

I just moved the second request within the then of the first request. NOTE: I did not syntax check this.

You can't run it synchronously, you have to wait for each promise to return a result before you can do something with that result. The normal way to do this is to nest code inside then blocks when using promises. Alternatively you can also use async/await with the latest version of typescript and you only have to change your component code as you are already returning the Promise type from your service. This makes code easier to read (IMO) although the emitted javascript code will still use function/callback nesting ( unless you are targeting es7 I believe, maybe someone will correct or confirm this ).

// note the use of async and await which gives the appearance of synchronous execution
async ngOnInit() {
    let id: number = this.route.snapshot.params['id'];

    const history = await this.productService.getHistory(id);
    this.historyProducts = history;

    const product = await this.productService.getProduct(id);
    let lastVersion: ProductHistory = this.createLastVersion(product);
    this.auditProducts.push(lastVersion);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM