Angular 5 - array staje się pusty w innej funkcji

0

Witam.
Czy może mi ktoś w końcu wyjaśnić, jak idiocie, jak zmienne w Angular 5 (TypeScript) działają?
Mam obiekt typu Dokument:

export class Dokument {
    ID:number;
    KodNabywcy:string;
    KodOdbiorcy:string;
    KodPlatnika:string;
    NumerDokumentu:string;
    Numerator:number;
    TypDokumentu:number;
    DataWystawienia:string;
    DataOperacji:string;
    TerminPlatnosci:string;
    FormaPlatnosci:number;
    Bufor:number;
    LiczonyOd:number;
    VAT:number;
    Netto:number;
    Brutto:number;
    Waluta:string;
    Pozycje:any[];
}

Ten dokument posiada tablice Pozycje, które uzupełniam za pomocą okienka modal z wypełnioną tabelką towarami z użyciem datatables i jego multiselect.
Dodawanie wybranych pozycji:

  addNewPozycje() {
    var table = $('#towaryTable').DataTable();
    let rows = table.rows('.selected').data();
    let tempPozycje = [];
    for (let i = 0; i < rows.length; i++) {
      const element = rows[i];
      let kod = element[1];
      this.api.GetTowarDoDokumentu(kod, this.dokument.KodNabywcy).subscribe((result) => { // z api ściągam więcej info na temat tego towaru 
        tempPozycje.push(result[0]); // do tymczasowej tablicy dodaje sobie rekordy jakie przyjdą z API
      }, (error) => console.log(error), () => {
      });
    }
    this.dokument.Pozycje = tempPozycje;
    this.przeliczWartoscDokumentu(this.dokument.LiczonyOd); // tutaj liczę wartość dokumentu na podstawie podanych pozycji
    console.log(this.dokument.Pozycje); // konsola wyrzuca mi poprawną tablice elementów w tym miejscu
    $('#new-event-modal').modal('hide');
    table.rows().deselect();
  }

Funkcja liczenia wartości dokumentu, która Pozycje.length ma równe 0:

przeliczWartoscDokumentu(typ: number) {
    this.dokument.Brutto = 0;
    this.dokument.Netto = 0;
    let rabat = 0;

    console.log(this.dokument.Pozycje.length); // length = 0... Why?!

    for (let i = 0; i < this.dokument.Pozycje.length; i++) {
      const pozycja = this.dokument.Pozycje[i];
      if (typ == 1) {
        pozycja.Cena = this.obliczCeneNetto(pozycja.Cena, pozycja.Stawka);
      }
      else {
        pozycja.Cena = pozycja.Cena;
      }
      pozycja.Wartosc = this.round(pozycja.Ilosc * pozycja.Cena);

      if (pozycja.Stawka == 0) {
        pozycja.Netto = pozycja.Wartosc;
        pozycja.Brutto = pozycja.Wartosc;
      }
      else {
        if (typ == 2) {
          pozycja.Brutto = pozycja.Wartosc;
          pozycja.Netto = this.obliczCeneNetto(pozycja.Wartosc, pozycja.Stawka);
        }
        else if (typ == 1) {
          pozycja.Netto = pozycja.Wartosc;
          pozycja.Brutto = this.obliczCeneBrutto(pozycja.Wartosc, pozycja.Stawka);
        }
      }
      this.dokument.Brutto += pozycja.Brutto;
      this.dokument.Netto += pozycja.Netto;
      this.dokument.VAT = this.round(this.round(this.dokument.Brutto) - this.round(this.dokument.Netto));
    }
  }
2

this.api.GetTowarDoDokumentu(...).subscribe rejestruje (kolejkuje) Promisea, który wykona się kiedyś w przyszłości (jest asynchroniczny).

Ty natomiast zakłądasz, że on wykona się od razu, i już kilka linijek niżej oczekujesz, że zmienna tempPozycje, wykorzystywana w tym Promiseie, będzie gotowa - rzecz jednak w tym, że część asynchroniczna kodu wykonuje się zawsze po części synchronicznej (poczytaj o event loop w JSie).

Przykład ilustrujący problem:

// Funkcja zwraca promise'a, który rozwiąże się (wykona) po 250 milisekundach, zwracając liczbę "128"
function buildMeSomePromise() {
	return new Promise((resolve) => {
		setTimeout(() => {
			resolve(128);
		}, 250);
	});
}

let number = 0;

// Tworzymy promise'a, kolejkując go do wykonania *kiedyś w przyszłości*
buildMeSomePromise().then((newNumber) => {
	number = newNumber;
});

// Nie czekamy na rezultat tamtego promise'a, tylko od razu lecimy dalej;
// Efekt: zmienna "number" wciąż trzyma starą wartość (zmieni ją dopiero za ok. 250 milisekund)
console.log(number);

// Po odczekaniu sekundy, zmienna będzie już miała nową wartość:
setTimeout(() => {
	console.log(number);
}, 1000);

W Twoim przypadku rozwiązaniem będzie podejście w stylu:

// Tablica "rowPromises" będzie zawierać listę wszystkich promise'ów utworzonych w wyniku przetwarzania wierszy:
let rowPromises = [];

// Utwórz promise'y:
// (można się także zabawić z Array.map! :-))
rows.forEach((row) => {
	let promise = this.api.GetTowarDoDokumentu(kod, this.dokument.KodNabywcy).subscribe(/* ... */) /* ... */;
	
	rowPromises.push(promise);
});

// Promise.all() tworzy nowy promise, czekający aż wszystkie podane promise'y w jego parametrze skończą swoje działanie
Promise.all(rowPromises).then(() => {
	/* reszta kodu tutaj */	
});

Zauważ, że wykorzystanie asynchroniczności powoduje, że reszta kodu musi się do niego dostosować - nie możesz mieszać kodu asynchronicznego z synchronicznym. To znaczy możesz... ale będziesz przetwarzał stare dane, a nie nowe (tak jak u Ciebie) :-)

A z drugiej strony: nie możesz np. wykonywać zapytań HTTP synchronicznie, ponieważ to zablokowałoby przeglądarkę na czas ich wykonywania (strona zamroziłaby się na kilka sekund, co jest dosyć niepożądanym efektem ubocznym).

0

Spodziewałem się, że problem leży w asynchroniczności ale chyba nie po to to powstało żeby teraz wszystko wciskać w setTimeout(). Chce się dowiedzieć co robię źle skoro console.log() ma dane, a funkcja zliczająca wartość dokumentu nie. Nawet kolejność nie ma znaczenia.

    this.dokument.Pozycje = tempPozycje;
    this.przeliczWartoscDokumentu(this.dokument.LiczonyOd); // najpierw funkcja licząca
    console.log(this.dokument.Pozycje); // później logowanie do konsoli

czy

    this.dokument.Pozycje = tempPozycje;
    console.log(this.dokument.Pozycje); // najpierw logowanie do konsoli
    this.przeliczWartoscDokumentu(this.dokument.LiczonyOd); // później funkcja licząca

Konsola pokazuje wszystko poprawnie. Jeśli synchronicznie będzie mi to działać to wole, bo nie będę siedział całą szychtę i tracił czas na taką głupotę. Mogę prosić o przykład synchronicznego pobierania danych z API?

0

Pokaż, co zwraca Ci konsola.

Jeśli synchronicznie będzie mi to działać to wole, bo nie będę siedział całą szychtę i tracił czas na taką głupotę

Dlaczego piszesz we frameworku opartym o asynchroniczność, bez uprzedniego zrozumienia na czym polega cały ten "bajer"? ;-)

0

Rozumiem na czym polega bajera (ogólnikowo), nie rozumiem dlaczego większą "władzę" ma konsola i chapie wszystko, a moje niezbędne funkcjałki nie mają co liczyć ;-)

0

Konsola nie ma większej władzy - nie masz tam przypadkiem jakiegoś gettera?
Rezultat pokazuje się od razu czy musisz coś kliknąć w konsoli, aby się dopiero załadował?

0

Podczas dodawania pozycji do dokumentu liczę jego wartość i wtedy się to wszystko dzieje.
Szczegóły masz na priv, strona jest w wersji testowej, nieoficjalnej.

1 użytkowników online, w tym zalogowanych: 0, gości: 1