Pattern for Retrieving Single Value from Angular 4 based Service - javascript

I am attempting to build an Angular 4 based service (backed by a C#-based RESTful API) which will allow for storing and retrieval of web-application wide settings. Something like a key-value pair based lookup for all common application settings.
The idea is this:
Retrieve all settings on start of the application from the C# WebApi based RESTful service into a client-side JavaScript array and stored in the Angular 4 service.
If any specific setting is needed, first look in the locally retrieved array for said setting and return that.
If said setting is not found, make a call to the previously mentioned WebAPI service for that specific setting to see if it is available and retrieve that. Also, push said retrieved setting in the client-side array so I don't have to make the call again until needed.
The problem I am having is this:
I want to return an Observable, even if I have the setting in the array locally, so that I can handle the situation of the web application having to wait for the setting to be retrieved.
I also want to handle situations where the API call for the specific setting fails.
See below for what I have now, any help appreciated.
'use strict';
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { EmptyObservable } from 'rxjs/observable/EmptyObservable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/share';
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { SettingAPIURLs } from '../global.urls';
import * as Interfaces from "../interfaces/models.interface";
import * as Classes from "../classes/models.classes";
#Injectable()
export class SettingsService {
private _settings: BehaviorSubject<Classes.Setting[]>;
private settingDataStore: {
settings: Classes.Setting[]
}
constructor(private http: Http) {
this.settingDataStore = { settings: [] }
}
loadSettings() {
this.http.get(SettingAPIURLs.GetSettings)
.map(response => response.json())
.subscribe(data => {
this.settingDataStore.settings = data;
this._settings.next(Object.assign({}, this.settingDataStore).settings);
}, error => {
console.log("There were errors in attempting to retrieve the web application's settings: " + error);
});
}
get CurrentSettings() {
return this._settings.asObservable().share();
}
retrieveSetting(SettingName: string): Observable<Classes.Setting> {
/*
I am lost as to what to do here.
*/
let returnedObservable: Observable<Classes.Setting> = new Observable<Classes.Setting>();
if (typeof (SettingName) === "undefined" || SettingName === null) {
return new EmptyObservable();
}
this.http.get(SettingAPIURLs.GetSetting + "?SettingName=" + SettingName)
.map(response => response.json())
.first();
}
}

Angular has a built in solution for your problem called resolve. This article explains how to use it:
https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html
The idea is that you have the data for the page loaded before even running the logic for the page.
The basic implementation is that you need to write a resolver that will go ahead and make your settings api call. Then you just need to hookup your routing to start using the resolver.

If I understood correctly , you want to send the setting if it exists else do http call . I am assuming your settings object is a key-value pair object. I am not sure why you have it is an array.
You can do something like this
// extract fetch part separately . If settings exist return it or do http call
fetchSettings(){
return this._settings.asObservable
.flatMap(settings => settings ?
Observable.of(settings) :
this.http.get(SettingAPIURLs.GetSettings)
.map(response => response.json())
.do(data => {
this.settingDataStore.settings = data;
this._settings.next(
Object.assign(
{},
this.settingDataStore).settings);
})
.catch(error => {
console.log("..... " + error);
});
}
loadSettings(){
this.fetchSettings.subscribe(data => {
console.log('load Settings success', data);
});
}
retrieveSetting(SettingName: string): Observable<Classes.Setting> {
this.fetchSettings()
.flatMap(settings => settings[SettingName]?
Observable.of(settings[SettingName]) :
this.http.get(SettingAPIURLs.GetSetting +
"?SettingName=" + SettingName)
.map(response => response.json())
.do(res=> this._settings.value[SettingName] = res)
);
}
If any of the above HTTP calls fail you will get an exception which you can handle in the component like this :
this.settingsService.retrieveSetting().subscribe(successFunction, failureFunction);
You can either show error message to user or consider it as blank based on your site requirements.

Related

How to store nuxtjs dynamically generated routes in vuex store

I'm trying to leverage nuxtjs SSG capabilities by creating a static web site where the pages content and navigation are fetched from an API.
I already found my way around on how to dynamically generate the routes by defining a module where I use the generate:before hook to fetch the pages content and routes. When creating the routes I store the page content as the route payload. The following code does just that and works as intended.
modules/dynamicRoutesGenerator.js
const generator = function () {
//Before hook to generate our custom routes
this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
generator.generateRoutes(await generateDynamicRoutes())
})
}
let generateDynamicRoutes = async function() {
//...
return routes
}
export default generator
Now the problem I'm facing is that I have some navigation components that need the generated routes and I was thinking to store them into the vuex store.
I tried the generate:done hook but I don't know how to get the vuex store context from there. What I ended up using was the nuxtServerInit() action because as stated in the docs:
If nuxt generate is ran, nuxtServerInit will be executed for every dynamic route generated.
This is exactly what I need so I'm trying to use it with the following code:
store/index.js
export const actions = {
nuxtServerInit (context, nuxtContext) {
context.commit("dynamicRoutes/addRoute", nuxtContext)
}
}
store/dynamicRoutes.js
export const state = () => ({
navMenuNivel0: {}
})
export const mutations = {
addRoute (state, { ssrContext }) {
//Ignore static generated routes
if (!ssrContext.payload || !ssrContext.payload.entrada) return
//If we match this condition then it's a nivel0 route
if (!ssrContext.payload.navMenuNivel0) {
console.log(JSON.stringify(state.navMenuNivel0, null, 2));
//Store nivel0 route, we could use url only but only _id is guaranteed to be unique
state.navMenuNivel0[ssrContext.payload._id] = {
url: ssrContext.url,
entrada: ssrContext.payload.entrada,
navMenuNivel1: []
}
console.log(JSON.stringify(state.navMenuNivel0, null, 2));
//Nivel1 route
} else {
//...
}
}
}
export const getters = {
navMenuNivel0: state => state.navMenuNivel0
}
The action is indeed called and I get all the expected values, however it seems like that with each call of nuxtServerInit() the store state gets reset. I printed the values in the console (because I'm not sure even if it's possible to debug this) and this is what they look like:
{}
{
"5fc2f4f15a691a0fe8d6d7e5": {
"url": "/A",
"entrada": "A",
"navMenuNivel1": []
}
}
{}
{
"5fc2f5115a691a0fe8d6d7e6": {
"url": "/B",
"entrada": "B",
"navMenuNivel1": []
}
}
I have searched all that I could on this subject and altough I didn't find an example similar to mine, I put all the pieces I could together and this was what I came up with.
My idea was to make only one request to the API (during build time), store everything in vuex then use that data in the components and pages.
Either there is a way of doing it better or I don't fully grasp the nuxtServerInit() action. I'm stuck and don't know how to solve this problem and can't see another solution.
If you made it this far thanks for your time!
I came up a with solution but I don't find it very elegant.
The idea is to store the the API requests data in a static file. Then create a plugin to have a $staticAPI object that expose the API data and some functions.
I used the build:before hook because it runs before generate:before and builder:extendPlugins which means that by the time the route generation or plugin creation happen, we already have the API data stored.
dynamicRoutesGenerator.js
const generator = function () {
//Add hook before build to create our static API files
this.nuxt.hook('build:before', async (plugins) => {
//Fetch the routes and pages from API
let navMenuRoutes = await APIService.fetchQuery(QueryService.navMenuRoutesQuery())
let pages = await APIService.fetchQuery(QueryService.paginasQuery())
//Cache the queries results into staticAPI file
APIService.saveStaticAPIData("navMenuRoutes", navMenuRoutes)
APIService.saveStaticAPIData("pages", pages)
})
//Before hook to generate our custom routes
this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
console.log('generate:before')
generator.generateRoutes(await generateDynamicRoutes())
})
}
//Here I can't find a way to access via $staticAPI
let generateDynamicRoutes = async function() {
let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
//...
}
The plugin staticAPI.js:
import APIService from '../services/APIService'
let fetchPage = function(fetchUrl) {
return this.pages.find(p => { return p.url === fetchUrl})
}
export default async (context, inject) => {
//Get routes and files from the files
let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
let pages = APIService.getStaticAPIData("pages")
//Put the objects and functions in the $staticAPI property
inject ('staticAPI', { navMenuRoutes, pages, fetchPage })
}
The APIService helper to save/load data to the file:
//...
let fs = require('fs');
let saveStaticAPIData = function (fileName = 'test', fileContent = '{}') {
fs.writeFileSync("./static-api-data/" + fileName + ".json", JSON.stringify(fileContent, null, 2));
}
let getStaticAPIData = function (fileName = '{}') {
let staticData = {};
try {
staticData = require("../static-api-data/" + fileName + ".json");
} catch (ex) {}
return staticData;
}
module.exports = { fetchQuery, apiUrl, saveStaticAPIData, getStaticAPIData }
nuxt.config.js
build: {
//Enable 'fs' module
extend (config, { isDev, isClient }) {
config.node = { fs: 'empty' }
}
},
plugins: [
{ src: '~/plugins/staticAPI.js', mode: 'server' }
],
buildModules: [
'#nuxtjs/style-resources',
'#/modules/staticAPIGenerator',
'#/modules/dynamicRoutesGenerator'
]

show the page after getting two different service calls

i want to show the page after getting the results of two
different service calls.service1 and service2 are two different
services
don't want to use second service call inside of first service
subscribe.service1 and service2 are two different services.
this.service1.getProfile1(id).subscribe((data1) => {
console.log(data1);
});
this.service2.getProfile2(id).subscribe((data2) => {
console.log(data2);
});
how to i found i got both service calls ?
You can use forkJoin from rxjs https://www.learnrxjs.io/operators/combination/forkjoin.html
import { forkJoin } from 'rxjs';
forkJoin(
this.service1.getProfile1(id),
this.service2.getProfile2(id)
).subscribe(([profile1, profile2]) => {
console.log(profile1, profile2);
});
You can merge the two observables with a fork join in this way:
import { forkJoin } from 'rxjs';
forkJoin([
this.service1.getProfile1(id),
this.service2.getProfile2(id),
]).subscribe(r => {
const data1 = r[0];
const data2 = r[1];
console.log(data1);
console.log(data2);
});
The calls are serialized, and then observable returns with an array of results. The position of the items reflects the order you created the forkJoin.
Actually in this particular use case you are navigating the user and trying to make a service call with two endpoints. Once you receive a response from those end point you are trying to merge it with fork join and send it to component as a observable
But in your question you are looking for a way to make the http calls even before redirecting to the page. There is a good approach for this use case in angular router.
You can specify what is the http calls which you want to perform even before user taken to a page.
Implement resolve in your service level and define that service under route resolve.
Example :
import { Injectable } from '#angular/core';
import { APIService } from './api.service';
import { Resolve } from '#angular/router';
import { ActivatedRouteSnapshot } from '#angular/router';
#Injectable()
export class APIResolver implements Resolve<any> {
constructor(private apiService: APIService) {}
resolve(route: ActivatedRouteSnapshot) {
return this.apiService.getItems(route.params.date);
}
}
Routes :
{
path: 'items/:date',
component: ItemsComponent,
resolve: { items: APIResolver }
}

http json data prints to console but not to page in Ionic 3

I know this is probably an easy solution. I am building an Ionic 3 app and was able to connect to a json data file and print the objects to the console, but I am unable to pass the item to my search function. The error I get on the page is Runtime Error - Cannot read property "filter" of undefined - I am guessing its because the item isn't being made available.
data.ts
import { Injectable } from "#angular/core";
import { Http } from "#angular/http";
import "rxjs/add/operator/map";
#Injectable()
export class Data {
items: any;
constructor(public http: Http) {
this.http
.get("./assets/data/plaques.json")
.map(res => res.json())
.subscribe(data => {
this.items = data;
console.log(data);
});
}
filterItems(searchTerm) {
return this.items.filter(item => {
return (
item.veteran_first.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1
);
});
}
}
You're trying to access this.items before it's been initialized.
http.get is an asynchronous function, and this.items may not be set when you access filterItems.
I highly recommend reading this question/answer which will go into greater understanding of the asynchronous nature of javascript.
could you please wrap it with zone() like this :
this.zone.run(() => {
this.items = data;
});

How to add a part of the acquired API to the array defined in component

For learning, I am trying to create a simple Todo application using Angular 2 + Django Rest Framework.
In order to immediately display the items saved in the input form, I want to implement the function of acquiring the latest one and displaying it.
JSON format WEB API for acquiring the latest one has the following structure.
function for getting this API is described in todo.service.ts
#Injectable()
export class TodoService {
todo: Todo[] = [];
newtodo: NewTodo[] = [];
private Url = `http://127.0.0.1:8000/api/todo/`
private headers = new Headers({'Content-Type': 'application/json'});
constructor(
private http: Http
){}
// Acquire one latest todo added
getNewTodo(): Promise<NewTodo[]> {
return this.http
.get(this.Url+"?limit=1")
.toPromise()
.then(res => res.json())
.catch(this.handleError)
}
Components are described in todo.component.ts
#Component({
selector: 'todo-list',
templateUrl: '../templates/todo-list.component.html',
styleUrls: ['../static/todo-list.component.css']
})
export class TodoListComponent {
todos: Todo[] = [];
newtodo: NewTodo[] = [];
newtodos: Todo[] = [];
#Input() todo: Todo = new Todo();
save(): void {
this.todoService
.create(this.todo);
}
}
The point I want to teach is the following points.
1.Execute service's getNewTodo () when save () of the component is executed and store the obtained return value in the array newtodo.
2.Since only the results part is necessary for the acquired JSON, pull out the results part, push it to the array newtodos and pass it to html.
For 1, I think that you should write the following description in save ()
this.todoService
.getNewTodo()
.then(newtodo => this.newtodo = newtodo);
I can not understand what to write about 2.
I'd like to tell you how to solve it.
You can step into the JSON and obtain the content of results with the following:
getNewTodo(): Promise<NewTodo[]> {
return this.http
.get(this.Url+"?limit=1")
.toPromise()
.then(res => res.json().results) // here
.catch(this.handleError)
}
You are not showing use the create-method in the service, but whatever it looks like, return a promise, so that we in the component can inside the callback call getNewTodo:
save() {
this.todoService
.create(this.todo)
.then(data => {
this.getNewTodo(); // here
})
}
and the getNewTodo that calls the method in the service:
getNewTodo() {
this.todoService
.getNewTodo()
.then(newtodo => {
this.newtodo = newtodo
});
}

Angular 2 run interface during http get

I've worked out how to import a json object from http get and list the results out via *ngFor.
What I can't work out is how to run an interface against it. My interface file is here:
import {Offer} from './offer';
and I have my http get request here:
constructor(private _httpService: HTTPTestService) { }
offers: "";
onTestGet() {
this._httpService.getOffers()
.subscribe(
data => { this.offers = data.offers; },
error => alert(error),
() => console.log("Finished")
);
}
But how do I run the "Offer" interface past the object I get back?
You need to be careful with interfaces in TypeScript. Interfaces are only for design and type checking but don't exist at runtime.
You can define an array of Offer for offers and cast the data you receive to Offer[]. But each element isn't of type Offer. This allows to check the structure of objects during compilation...
Here is a sample:
constructor(private _httpService: HTTPTestService) { }
offers: Offer[]; // <----
onTestGet() {
this._httpService.getOffers()
.subscribe(
data => { this.offers = <Offer[]>data.offers; }, // <----
error => alert(error),
() => console.log("Finished")
);
}

Categories

Resources