Angular function in controller not getting called after promise - javascript

I am building an Angular 1.5 app using the component structure. After the promise comes back from the $http call in the service, I am trying to call another function to filter the dataset before it is displayed on the UI.
However, the filterApps function is not getting called.
Also...in the filterApps function I am trying to compare to arrays of objects and return back the ones that have the same name. Is this the best way to go about this or is there a cleaner way?
Controller :
import allApps from '../../resources/data/application_data.js';
class HomeController {
/*#ngInject*/
constructor(ItemsService) {
this.itemsService = ItemsService;
this.displayApps = [];
}
$onInit() {
this.itemsService
.getItems()
.success((apps) => this.filterApps(apps));
}
filterApps(siteApps) {
this.displayApps = allApps.applications.filter((app) => {
siteApps.applications.map((siteApp) => {
if(siteApp.name === app.name) {
return app;
}
})
});
}
}
export default HomeController;

I don't see any reason that filterApps method isn't geting call(as you already commented that success function is getting called). I guess you're just checking nothing has been carried in displayApps variable. The real problem is you have not return internal map function result to filter. So that's why nothing gets return.
Code
filterApps(siteApps) {
this.displayApps = allApps.applications.filter((app) => {
//returning map function result.
return siteApps.applications.map((siteApp) => {
if(siteApp.name === app.name) {
return app;
}
})
});
}

Related

API call returns empty object when called from inside Angular service

I'm getting this only when I subscribe to the function that makes api call from inside the Angular service, so the object that subscribes to the function is empty. Here's my code snippet from my service:
getSchedules(): Observable<Schedule[]> {
this.http.get<TempSchedules[]>(this.apiUrl).subscribe(x => this.temp = x);
this.temp.forEach((e, i) => {
// Do something, this loop is never executed because this.temp is empty
});
// Some processing here
return something; }
Here is my http.get function somewhere inside the service:
getTempSchedules(): Observable<TempSchedules[]> {
return this.http.get<TempSchedules[]>(this.apiUrl);
}
From the above, this.temp is empty. Why is that?
I called the above function in the service constructor as
constructor(private http:HttpClient) {
this.getTempSchedules().subscribe(x => this.temp = x);
}
Here is a code snippet from a component that calls that function in the service:
ngOnInit(): void {
this.scheduleService.getTempSchedules().subscribe(x => this.tempSchedules = x);
}
The component works fine, so when I use the value this.tempSchedules in the html it is displayed correctly. What am I missing here?
It is not working because you are not getting the way observable works. It is async process and you need to be in subscribe block to get it. In case you want to do some funky stuff with the response before returning it to the component, then you should use map
getTempSchedules(): Observable<Schedule[]> {
return this.http.get<TempSchedules[]>(this.apiUrl)
.pipe(map(res => {
return res.forEach(() => {
// Do something, this loop will be executed
})
})) }
use it in component as :
ngOnInit(): void {
this.scheduleService.getTempSchedules().subscribe(x => this.tempSchedules = x);
}

Communicating between Angular components and non-Angular components: Returning data from an observable coming from a callback

I have an application that requires that I use non-Angular JavaScript for certain things. To trigger an action in the Angular component, I'm passing a down a callback to the non-Angular component. When the callback is triggered, an observable runs on the Angular component (doing an http call). This works but the only piece of the puzzle I'm having trouble with is getting the data returned from this observable passed back down to the non-Angular component somehow. My actual application is fairly complex so I've created a Stackblitz for a much more simplified version to make it easier to see what I'm doing.
This is tricky for me as the GET call in doStuff is async, so I can't just return the results. I'd have some ideas on how to work around this in a pure Angular app... but I'm not sure how to accomplish this when sharing data between an Angular component and a Non-Angular one.
app.component.ts:
export class AppComponent {
constructor(private http: HttpClient){}
doStuff() {
let randomNum = this.getRandomInt(2); // Simulate different http responses
this.http.get<any>(`https://jsonplaceholder.typicode.com/todos/${randomNum}`).subscribe(x => {
if (x === 1) {
// Here is where I want to share data with the non-Angular component
console.log(x.id);
} else {
// Here is where I want to share data with the non-Angular component
console.log(x.id);
}
});
}
ngOnInit() {
var x = new NonAngularComponent(this.doStuff.bind(this));
}
private getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max)) + 1;
}
}
NonAngularComponent.ts:
export class NonAngularComponent {
constructor(private onSave: () => void) {
this.init()
}
init() {
const newElement = document.createElement('button');
newElement.innerHTML = 'Click';
newElement.addEventListener('click', () => {
this.onSave(); // Works, but now I need to do something with the results of doStuff()
});
document.getElementById('foo').append(newElement);
}
}
Any suggestions would be much appreciated.
It would be better to return Observable from your doStuff() method and use tap operator if you want to have some side effect in Angular component:
doStuff() {
let randomNum = this.getRandomInt(2);
return this.http.get<any>(`https://jsonplaceholder.typicode.com/todos/${randomNum}`).pipe(tap(x => {
if (x === 1) {
// Here is where I want to share data with the non-Angular component
console.log(x.id);
} else {
// Here is where I want to share data with the non-Angular component
console.log(x.id);
}
}));
}
non-angular.component.ts
newElement.addEventListener('click', () => {
this.onSave().subscribe(res => {
// do whatever you want
});
});
Forked Stackblitz
I think that the easiest solution would be to simply have an instance of your NonAngularComponent inside the AppComponent
this.nonAngularComponent = new NonAngularComponent(this.doStuff.bind(this));
And in the callback simply call the method you want from the NonAngularComponent like so:
doStuff() {
let randomNum = this.getRandomInt(2);
this.http
.get<any>(`https://jsonplaceholder.typicode.com/todos/${randomNum}`)
.subscribe(x => {
if (x === 1) {
// Here is where I want to share data with the non-Angular component
// console.log(x.id);
this.nonAngularComponent.doSomething(x);
} else {
// Here is where I want to share data with the non-Angular component
// console.log(x.id);
this.nonAngularComponent.doSomething(x);
}
});
}
doSomething method:
public doSomething(result) {
console.log("Non-Angular component received result", result);
}
And console output:
Stackblitz: https://stackblitz.com/edit/angular-tddc7q?file=src%2Fapp%2FNonAngularComponent.ts

Get observable return value without subscribing in calling class

In TypeScript / Angular, you would usually call a function that returns an observable and subscribe to it in a component like this:
this.productsService.getProduct().subscribe((product) => { this.product = product });
This is fine when the code runs in a class that manages data, but in my opinion this should not be handled in the component. I may be wrong but i think the job of a component should be to ask for and display data without handling how the it is retrieved.
In the angular template you can do this to subscribe to and display the result of an observable:
<h1>{{ product.title | async }}</h1>
Is it possible to have something like this in the component class? My component displays a form and checks if a date is valid after input. Submitting the form is blocked until the value is valid and i want to keep all the logic behind it in the service which should subscribe to the AJAX call, the component only checks if it got a valid date.
class FormComponent {
datechangeCallback(date) {
this.dateIsValid$ = this.dateService.checkDate(date);
}
submit() {
if (this.dateIsValid$ === true) {
// handle form submission...
}
}
}
You can convert rxjs Observables to ES6 Promises and then use the async-await syntax to get the data without observable subscription.
Service:
export class DateService {
constructor(private http: HttpClient) {}
async isDateValid(date): Promise<boolean> {
let data = await this.http.post(url, date, httpOptions).toPromise();
let isValid: boolean;
// perform your validation and logic below and store the result in isValid variable
return isValid;
}
}
Component:
class FormComponent {
async datechangeCallback(date) {
this.dateIsValid = await this.dateService.isDateValid(date);
}
submit() {
if (this.dateIsValid) {
// handle form submission...
}
}
}
P.S:
If this is a simple HTTP request, which completes on receiving one value, then using Promises won't hurt. But if this obersvable produces some continuous stream of values, then using Promises isn't the best solution and you have to revert back to rxjs observables.
The cleanest way IMHO, using 7.4.0 < RxJS < 8
import { of, from, tap, firstValueFrom } from 'rxjs';
const asyncFoo = () => {
return from(
firstValueFrom(
of('World').pipe(
tap((foo) => {
console.info(foo);
})
)
)
);
};
asyncFoo();
// Outputs "World" once
asyncFoo().subscribe((foo) => console.info(foo));
// Outputs "World" twice
The "more cleanest" way would be having a factory (in some service) to build these optionally subscribeable function returns...
Something like this:
const buildObs = (obs) => {
return from(firstValueFrom(obs));
};
const asyncFoo = () => {
return buildObs(
of('World').pipe(
tap((foo) => {
console.info(foo);
})
)
);
};

Vue.js - Custom plugin function is undefined

I have created a simple plugin for my Vue2.js project to get some lists for my select fields in forms. The plugin should make some axios call and return the response.
const ListSelector = {
install (Vue){
Vue.mixin({
methods: {
getSubscriberType: function() {
this.$http
.get('/web/admin/contract/subscribers')
.then(function(response) { return response.data })
},
//other methods (they do not work the same...)...
}
})
}
}
export default ListSelector
I registered the plugin in my main.js
import ListSelector from './backend/selectable'
Vue.use(ListSelector)
Now, if i try to call the plugin method in a component, i get that is undefined
<template>
...
<b-form-select v-model="form.type" :options="options" id="type" required></b-form-select>
...
</template>
<script>
export default {
data(){
return {
options: {}
}
},
mounted(){
this.options = this.getSubscriberType()
}
}
<script>
I get that this.getSubscriberType() is undefined
EDIT: i actually see that the function is fired (i put an alert in it... but if i do a console.log(this.getSubscriberType()) in the component, i get undefined...
It looks like your method never actually returns anything. $http.get() returns a promise, and promises are by their nature asyncronous.
const ListSelector = {
install (Vue){
Vue.mixin({
methods: {
getSubscriberType: function() {
this.$http
.get('/web/admin/contract/subscribers') //returns a new promise
.then(function(response) { //Executes after the promise resolves
return response.data //not in the same scope as getSubscriberType
})
// getSubscriberType completes without a return value, hence undefined
}
}
})
}
}
Because the function resolves before the promise is resolved, the getSubscriberType does not even have the response available to it.
Instead, you want to return the promise.
getSubscriberType: function() {
return this.$http.get('/web/admin/contract/subscribers') // now returns promise
}
Then you would do whatever you need to do with the response locally when it completes
var self = this // gives you a way to access local scope when the promise resolves
getSubscriberType().then(funciton(response) {
self.subscriberType = response.data
})
In this example, it would set the subscriberType value on the component when the http call completes successfully.

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);
});
}
}

Categories

Resources