Converting multiple nested promises and array of promises to Observables - javascript

I am in the process of converting a AngularJS app to Angular 7 in TypeScript.
I am having some problems with converting some complex nested promises to Observables.
Here is an example of the code I'm dealing with:
signup.component.js
function SomethingSignupController(somethingApplication) {
function activate() {
getApplication();
}
function getApplication() {
vm.getFromServer = false;
vm.promises = [];
SomethingRepo.get().then(function(application) {
vm.getFromServer = true;
vm.application = application;
vm.promises.push(Something.getCompany().then(function(company) {
vm.company = company;
if (vm.company.structure === ‘more_25’) {
return SomethingRepo.getAllOwners().then(function(owners) {
vm.owners = owners;
for(var i = 0; i < vm.owners.length; i++) {
vm.promises.push(getOwnerFiles(vm.owners[i]));
}
}
}
}
vm.promises.push(SomethingRepo.getSomethingOne().then(function(somethingOne) {
vm.somethingOne = somethingOne;
}
vm.promises.push(SomethingRepo.getSomethingTwo().then(function(somethingTwo) {
vm.somethingTwo = somethingTwo;
}
vm.promises.push(SomethingRepo.getSomethingThree().then(function(somethingThree) {
vm.somethingThree = somethingThree;
}
/* and a few more like the above */
$q.all(vm.promises).then(function(){
postGet();
}).finally(function() {
vm.promises = [];
});
}
}
function postGet() {
/* does something with the data acquired from SomethingRepo */
}
/* when an application is send */
function send() {
somethingApplication.promises = [];
somethingApplication.errors = [];
if (vm.getFromServer) {
update();
} else {
create();
}
}
function update() {
somethingApplication.promises.push(SomethingRepo.update(vm.application).then(angular.noop, function(error) {
somethingApplication.parseErrors(error, ‘Some error’);
}));
patchInfo();
}
function create() {
}
function patchInfo() {
somethingApplication.promises.push(SomethingRepo.patchAccount(vm.account).then(angular.noop, function(error) {
somethingApplication.parseErrors(error, ‘Account error: ‘);
}
/* a few more patches */
$q.all(somethingApplication.promises).then(function() {
/* display dialog */
}, angular.noop).finally(function() {
postGet();
somethingApplication.promises = [];
if (somethingApplication.errors.length >= 1) {
vm.errors = somethingApplication.errors;
}
});
}
}
somethingApplication.service.js
function somethingApplication(SomethingRepo) {
var promises = [], errors = [];
var service = {
promises: promises;
errors = errors;
parseErrors: parseErrors;
};
return service;
function parseErrors(error, base_string) {
angular.forEach(error.data.erros, function(value_params, key_params) {
this.errors.push(base_string + ‘ ‘ + key_params.replace(/_/g, ‘ ‘) + ‘ ‘ + value_params);
}, this);
}
}
somethingRepo.js
function SomethingRepo(Server) {
function get() {
return Server.get(‘/something/application’, null, {noGlobal: true});
}
}
I have reduced the files, but they consist of more code like this.
The point of the controller is to create or update an application for another website. On my website I have a form of fields corresponding to the form on the other website. If you already have filed for an application, but want to update it, the info you already filed are loaded from the other website.
The problem is, in order to create or update an application, a lot of different endpoints requested og posted to.
In AngularJS I store the promises from each request and run them asynchronously in the end. In TypeScript and Angular I want to use Observables and subscribe to the data change.
How do I get started? How do I subscribe to an Observable the requires parameters from another Observable? Any advice how to proceed?

Here's an example demonstrating how you can easily use observables in your scenario -
Your service would be something like this -
import { Injectable } from '#angular/core';
import { AppConstants } from '../../app.constants';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class ExampleService {
constructor(private appconstants: AppConstants, private http: HttpClient) { }
get() {
return this.http.get(this.appconstants.apiUrl);
}
getSomethingOne() {
return this.http.get(this.appconstants.apiUrl1);
}
getSomethingTwo() {
return this.http.get(this.appconstants.apiUrl2);
}
}
Then simply use it in your component as follows -
import { Component } from '#angular/core';
import { forkJoin } from 'rxjs';
import { ExampleService } from '../services/example.service';
#Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent {
data;
dataOne;
dataTwo;
constructor(private exampleService: ExampleService) { }
getApplication() {
const combined = forkJoin([
this.exampleService.get(),
this.exampleService.getSomethingOne(),
this.exampleService.getSomethingTwo()
]);
combined.subscribe(res => {
this.data = res[0];
this.dataOne = res[1];
this.dataTwo = res[2];
});
}
}

Related

Cannot read properties of undefined on synchronous call to a rest api

I'm new to angular and I wasn't sure how to implement synchronous api calls. I implemented async/await from a few articles I read but it still seems like the variables are undefined meaning the console is printing before even initializing the variable. I need it to be synchronous because code further down the cycle function depends on accurate variables.
I'm making a small program where people can upload their own images and it will be displayed on the stage component. I'm saving the images as a blob on a mysql database and retrieving them one at a time depending on the names provided in my nameList array variable
What am I doing wrong when calling the api via synchronous call?
stage.component.html
<div class="container">
<div class="slideshow" *ngIf="retrievedImage">
<ng-container>
<img [src]="retrievedImage"/>
<h1 *ngIf="!database_populated" style="color: red;">No Photo's to show. Please go back and upload</h1>
</ng-container>
</div>
</div>
stage.component.ts
import { HttpClient } from '#angular/common/http';
import { Component, OnInit } from '#angular/core';
import { interval } from 'rxjs';
import { ImagingService } from '../../services/imaging.service';
#Component({
selector: 'app-stage',
templateUrl: './stage.component.html',
styleUrls: ['./stage.component.css']
})
export class StageComponent implements OnInit {
constructor(private httpClient: HttpClient, private imageService: ImagingService) { }
retrieveResponse: any;
public namesList: any;
imageName: string = "eating.jpg";
base64Data: any;
retrievedImage: any = null;
currentImage = 0;
public database_populated: boolean = false;
totalImages: any;
ngOnInit(): void {
this.checkCount().then(count => {
if (count > 0 ) {
this.database_populated = true
console.log("database is populated. going to cycle")
this.cycle()
}
else {
this.database_populated = false;
}
}) }
cycle(){
console.log("entering cycle")
interval(10000).subscribe(x =>
{
// update how many images there are in the database
this.checkCount().then(data => {
this.totalImages = data
})
console.log(this.totalImages)
//update the list of image names found in the database
this.updateNamesList().then(nameList => {
this.namesList = nameList;
})
console.log(this.namesList)
if (this.currentImage == this.totalImages){
console.log("inside mod")
this.currentImage = this.currentImage % this.totalImages
}
else
{
console.log("printing pictures")
// display the Nth image in the list
this.imageName = this.namesList[this.currentImage]
// increment the image count in case there is another image added to the database
this.currentImage = this.currentImage + 1
this.getImage()
}
});
}
getImage() {
//Make a call to Sprinf Boot to get the Image Bytes.
this.httpClient.get('http://localhost:8080/halloween/get/' + this.imageName)
.subscribe(
res => {
this.retrieveResponse = res;
this.base64Data = this.retrieveResponse.picByte;
this.retrievedImage = 'data:image/jpeg;base64,' + this.base64Data;
}
);
}
async updateNamesList(){
return await this.imageService.updateNamesList()
}
async checkCount(){
return await this.imageService.checkCount()
}
}
imaging.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class ImagingService {
constructor(private httpClient: HttpClient) { }
public updateNamesList() {
return this.httpClient.get('http://localhost:8080/halloween/allnames').toPromise();
}
public checkCount() {
return this.httpClient.get('http://localhost:8080/halloween/check').toPromise();
}
}
this is a snippet of the browser console errors and it shows the variables as undefined even though I place the promise prior to the console.log
Your code will not work with asynch. Here is the order of execution.
// command 1
this.checkCount().then(data => {
//command 3
this.totalImages = data
});
// command 2, totalImages will be undefined.
console.log(this.totalImages)
There is no guarantee about time at command 2, because we fetch data through network, so delay time may take few seconds.
You can await the result of checkCount to make sure we have data through rest api.:
this.totalImages = await this.checkCount();
Or you can do other things after rest api have an data.
this.checkCount().then(data => {
this.totalImages = data
doSomethingWithTotalImagesHere();
});

How to get data value in regular js file from vue component?

I have component MyComponent.vue where I have data value that constantly changes. I want to pass this value to javascript file(js file should know about changes of value everytime)
Why do I do that? Because my regular js file is a service layer for axios methods. I can import this file in many other components. The file contains axios methods and urls are dynamic.
I want those urls depend on data variable. This data variable comes from MyComponent.js
So the main goal is to make dynamic urls of axios that depend on data variable
I tried some code but it doesn't work, because js file(CategoryService.js) know nothing about this.categoryNumber.
MyComponent.vue:
<script>
export default {
data() {
return {
categoryNumber: 1
}
}
}
</script>
CategoryService.js
import http from "../../../http-common";
let category = "category1";
if (this.categoryNumber === 1) {
category = "category1";
} if (this.categoryNumber === 2) {
category = "category2";
}
class CategoryService {
get(id) {
return http.get(`/${category}/${id}`);
}
update(id, data) {
return http.put(`/${category}/${id}`, data);
}
create(data) {
return http.post(`/${category}`, data);
}
delete(id) {
return http.delete(`/${category}/${id}`);
}
getAll() {
return http.get(`/${category}/all`);
}
}
export default new CategoryService();
So with a bit of refactoring, you could easily get this working.
First of all, I would put the if/else logic of your class into it.
For convenience and scalability, I would use a Vuex store that will keep track of your categoryNumber and share it accross all your components.
Then I would bind my service to my Vue instance so I can easily access it in all my components as well as the store and I would pass the latter to my class as a parameter.
For the last part, I don't know the logic in the http-common file so the code I will show you is a bit nasty. But depending on wether or not you bound 'http' to axios, you could make use of axios interceptors to call the getCategoryNumber() method in every request.
Here's an idea of the implementation I would go for:
const CategoryService = class CategoryService {
constructor(store) {
this._store = store;
this.category = "category1";
}
getCategoryNumber() {
if (this._store.state.categoryNumber === 1) {
this.category = "category1";
}
if (this._store.state.categoryNumber === 2) {
this.category = "category2";
}
console.log(this.category); // for demo puprose
}
get(id) {
this.getCategoryNumber(); // We could use axios request interceptor instead of calling that in every route, but that works !
return http.get(`/${this.category}/${id}`);
}
update(id, data) {
this.getCategoryNumber();
return http.put(`/${this.category}/${id}`, data);
}
create(data) {
this.getCategoryNumber();
return http.post(`/${this.category}`, data);
}
delete(id) {
this.getCategoryNumber();
return http.delete(`/${this.category}/${id}`);
}
getAll() {
this.getCategoryNumber();
return http.get(`/${this.category}/all`);
}
}
const store = new Vuex.Store({
state: {
categoryNumber: 1
},
mutations: {
setCategoryNumber(state, payload) {
state.categoryNumber = payload;
}
}
});
// Bind your service to the Vue prototype so you can easily use it in any component with 'this.$service'
// pass it the store instance as parameter
Vue.prototype.$service = new CategoryService(store);
new Vue({
el: "#app",
store, // dont forget to bind your store to your Vue instance
methods: {
updateCategoryNumber() {
// Put here any logic to update the number
this.categoryNumber = this.categoryNumber === 1 ? 2 : 1;
this.checkServiceCategoryValue();
},
checkServiceCategoryValue() {
// for demonstration purpose
this.$service.getCategoryNumber();
}
},
computed: {
// Look for the store value and update it
categoryNumber: {
get() {
return this.$store.state.categoryNumber;
},
set(value) {
this.$store.commit("setCategoryNumber", value);
}
}
}
});
<div id="app">
<h2>number: {{ categoryNumber }}</h2>
<button type="button" #click="updateCategoryNumber()">
updateCategoryNumber
</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex#2.0.0"></script>
Thanks to #Solar
I just added one more parameter for all urls and put the number of category to it
CategoryService.js:
class CategoryOneService {
get(id, category) {
return http.get(`/${category}/${id}`);
}
getAll(category) {
return http.get(`/${category}/all`);
}
}
functions.js:
let catNum = "";
function getQuestion() {
if (this.categoryNumber === 1) {
catNum = "category1";
}
if (this.categoryNumber === 2) {
catNum = "category2";
}
let questionId = this.questionNumber;
CategoryOneService.get(questionId, catNum)
.then(response => {
this.question = response.data.question;
this.answer = response.data.answer;
})
.catch(error => {
console.log(error);
});
}

Returning inner $http promise

I have two services:
ProductService
CartService
And a Controller:
ShoppingController
ShoppingController needs to download the cart from the server. In order for CartService to do this, ProductService must first download the products.
ProductService.js
ProductService.DownloadProducts = function(){
if(alreadyHaveDownloadedProducts){
return {
success: function (fn) {
fn(products);
}
};
}else{
if(getProductsPromise== null){
getProductsPromise= $http.post("Api/GetProducts")
}
return getProductsPromise;
}
CartService.js
CartService.DownloadCart = function(){
ProductService.DownloadProducts().success(function(){
if(alreadyHaveDownloadedCart){
//Dont need to go to server
return {
success: function (fn) {
fn(cart);
}
};
}else{
if(getCartPromise == null){
getCartPromise = $http.post("Api/GetCart")
}
return getCartPromise; //<= The promise I actually want to return
}
})
}
ProductService.DownloadProducts
ShoppingController.js
CartService.DownloadCart().success(function(){DisplayCart()});
This approach works nicely so far, because if the ProductService has already been called on a different page, I don't need to go back to the server. Same for cart service.The issue is I currently can't return the getCartPromise as it hasn't been created until the async GetProducts has returned
Is it possible to structure this so I can return the inner promise to ShoppingController while keeping the nice .success() syntax?
My way is similar to yours, but instead of saving some alreadyHaveDownloadedCart (boolean flag), i'm caching the promise it self, and then returns it.
I'm caching the promise for the data on the service class, and then returning it if it is exists otherwise initialize a server call.
Something like that:
class ProductService {
constructor() {
this.productsPromise = null;
}
DownloadProducts() {
if(!this.productsPromise) {
this.productsPromise = $http.post('Api/GetProducts');
this.productsPromise.then(products => this.products = products);
}
return this.productsPromise.then(() => this.products);
}
}
class CartService {
constructor(ProductService) {
this.ProductService = ProductService;
this.cartPromise = null;
}
DownloadCart() {
return this.ProductService.DownloadProducts().success(() => {
if (!this.cartPromise) {
this.cartPromise = $http.post('Api/GetCart');
this.cartPromise.then((cart) => {
this.cart = cart;
});
}
return this.cartPromise.then(() => this.cart);
});
}
}

Javascript ES6 MV(C) TypeError

I'm very new to JavaScript MVC logic. I'm trying to create a simple app.
I've set up everything like in the example (In seperate files ofc). Data.js which is not shown contains an object with some data, and template.js contains my html template strings.
// CONTROLLER
import Model from './models/model';
import View from './views/view';
import Controller from './controllers/controller';
import { $on } from './utility/utility';
export default class Controller {
constructor(Model, View) {
this.model = Model;
this.view = View;
};
loadInit() {
console.log("controller render");
this.model.test();
// this.view.render(this.model.data);
};
}
// MODEL
import data from '../config/data'
data.initialize();
export default class Model {
constructor() {
this.data = data;
};
test() {
console.log("Test works");
};
generateLists() {
window.infiniteList = [];
var articlesList = 'http://lib.lrytas.lt/api/articlesList/article.php?term=/lrytas/' + data.blockTag.slug + '/*&domain=' + data.blockTag.domain;
$.get(articlesList, function(response) {
for(let i = 0; i < response.blockTop7.length; i++) {
if (response.blockTop7[i].n18 == "0") {
window.infiniteList.push(response.blockTop7[i].main_id);
}
};
for(let i = 0; i < response.newBlock.length; i++) {
if (response.newBlock[i].n18 == "0") {
window.infiniteList.push(response.newBlock[i].main_id);
}
};
});
console.log(infiniteList);
};
loadArticle(data) {
$.get(lr_config.kolumbusApi + 'query/?kpm3id=' + infiniteList[0] + '&ret_fields=props', function(response) {
let result = response.result["0"].props;
this.data.update(result);
});
window.infiniteList.splice(0,1);
};
}
// VIEW
import config from '../config/config'
import Template from '../template/template'
import data from '../config/data'
data.initialize();
export default class View {
constructor() {
this.el = config.mainWrapper;
};
render(Data) {
console.log("view render");
this.el.innerHTML += Template(Data);
};
}
// APP.JS
class App {
constructor() {
this.model = new Model();
this.view = new View();
this.controller = new Controller(Model, View);
};
}
const app = new App();
const init = () => {
app.controller.loadInit();
}
$on(window, 'load', init);
Heres JSBIN.
Now the problem is when I'm trying to call init function on load in main.js. It goes to controller, calls the loadInit() function which then should call this.model.test(), but it wouldnt work and it gives me the following error
"Uncaught TypeError: this.model.test is not a function".
I've been trying to find a solution for past couple hours but I'm really lost here.
Any help would be greatly appreciated. Thanks!

Angular 2 ES6/7 Eventemitter update other Component

i want to share data between components, so im implemented a Service which has an EventEmitter.
My Service looks like this:
#Injectable()
export class LanguageService {
constructor() {
this.languageEventEmitter = new EventEmitter();
this.languages = [];
this.setLanguages();
}
setLanguages() {
var self = this;
axios.get('/api/' + api.version + '/' + api.language)
.then(function (response) {
_.each(response.data, function (language) {
language.selected = false;
self.languages.push(language);
});
self.languageEventEmitter.emit(self.languages);
})
.catch(function (response) {
});
}
getLanguages() {
return this.languages;
}
toggleSelection(language) {
var self = this;
language.selected = !language.selected;
self.languages.push(language);
self.languageEventEmitter.emit(self.languages);
}
}
I have to components, which are subscribing to the service like this:
self.languageService.languageEventEmitter.subscribe((newLanguages) => {
_.each(newLanguages, function (language) {
self.updateLanguages(language);
});
});
When both components are loaded, the language arrays get filled as i wish.
This is the first component:
export class LanguageComponent {
static get parameters() {
return [[LanguageService]];
}
constructor(languageService) {
var self = this;
this.languageService = languageService;
this.languages = [];
this.setLanguages();
}
setLanguages() {
var self = this;
self.languageService.languageEventEmitter.subscribe((newLanguages) => {
_.each(newLanguages, function (language) {
self.updateLanguages(language);
})
});
}
updateLanguages(newLanguage) {
var self = this;
if (!newLanguage) {
return;
}
var match = _.find(self.languages, function (language) {
return newLanguage._id === language._id;
});
if (!match) {
self.languages.push(newLanguage);
}
else {
_.forOwn(newLanguage, function (value, key) {
match[key] = value;
})
}
toggleLanguageSelection(language) {
var self = this;
self.languageService.toggleSelection(language)
}
}
When LanguageComponent executes the function toggleLanguageSelection() which triggered by a click event, the other component, which subscribes like this:
self.languageService.languageEventEmitter.subscribe((newLanguages) => {
_.each(newLanguages, function (language) {
self.updateLanguages(language);
})
});
doesn't get notfiefied of the change. I think this happens because both component get a different instance of my LanguageService, but i'm not sure about that. I also tried to create a singleton, but angular'2 di doesn't work then anymore. What is the reason for this issue and how can i solve this ?
You need to define your shared service when bootstrapping your application:
bootstrap(AppComponent, [ SharedService ]);
and not defining it again within the providers attribute of your components. This way you will have a single instance of the service for the whole application. Components can leverage it to communicate together.
This is because of the "hierarchical injectors" feature of Angular2. For more details, see this question:
What's the best way to inject one service into another in angular 2 (Beta)?

Categories

Resources