Angular 6 - Initialize service with async calls before dependency injection - javascript

I have a service which based on the environment file, loads up a configuration object in memory. However, the reading of the settings is asynchronous and the application starts before even all the settings are loaded and crashes. Is there any way to 'await' for these functions before the dependency injection is complete.
My service :
import { Injectable } from '#angular/core';
import { IAppConfig } from '../models/app-config.model';
#Injectable()
export class AppConfig {
settings: IAppConfig;
version: any;
constructor() {
this.loadConfig();
}
// reads env.json file
// based on which environment it is loads config setting from
// environment specific config settings.
public loadConfig() {
return new Promise((resolve, reject) => {
const envFile = '/env.json';
this.readJsonFile(envFile).
then((envData) => {
const configFile = `assets/appconfigs/config.${envData.env}.json`;
this.version = envData.version;
this.readJsonFile(configFile).
then((configsettings) => {
this.settings = configsettings;
resolve(this.settings);
});
});
});
}
// reads json file and returns the json object promise
public readJsonFile(jsonUrl: string): any {
return new Promise((resolve, reject) => {
let retObject: any;
const xhr = new XMLHttpRequest();
xhr.overrideMimeType('application/json');
xhr.open('GET', jsonUrl, true);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
retObject = JSON.parse(xhr.responseText);
resolve(retObject);
} else {
reject(`Could not load file '${jsonUrl}': ${xhr.status}`);
}
}
};
xhr.send(null);
});
}
}
I want the settings object to be loaded before the application fires up. The other way was to make this class static and call loadConfig but it is a nightmare for testbeds. Is there anything I could specify when providing the service in the module?

I'm not sure about this solution (untested code), but I guess you could async/await everything.
Is there any reason why you're not using Angular's http service, and instead, making a manual XMLHttpRequest?
It could be something like this :
constructor() {
this.run()
}
async run() {
await this.loadConfig()
}
// reads env.json file
// based on which environment it is loads config setting from
// environment specific config settings.
public loadConfig() {
return new Promise( async (resolve, reject) => {
const envFile = '/env.json';
let envData = await this.readJsonFile(envFile)
const configFile = `assets/appconfigs/config.${envData.env}.json`;
this.version = envData.version;
this.settings = await this.readJsonFile(configFile);
resolve(); // No need to pass this.settings to resolve()
});
}
// reads json file and returns the json object promise
public readJsonFile(jsonUrl: string): any {
return this.http
.get(jsonUrl)
.map(res => res.json())
.toPromise()
}

I want the settings object to be loaded before the application fires up.
You could/should make use of the injection token APP_INITIALIZER: angular.io/APP_INITIALIZER
In your case, something like below code would suffice.
In your AppModule:
{
provide: APP_INITIALIZER,
useFactory: initApp,
deps: [AppConfig]
}
export function initApp(appConfig: AppConfig) {
return () => appConfig.loadConfig();
}
More information on factory providers: angular.io/FactoryProvider

Related

Using the js library in the flutter project

I'm new to flutter, I have a flutter project that I'm building for android, ios and web. And only in the web build do I need to use the js library to encrypt api requests. Here is this library: https://www.npmjs.com/package/#expressms/smartapp-bridge and she's not very good, as far as I'm concerned. This is a requirement of the client and he provides this library. I can't rewrite it in dart. I have to use js callbacks for encryption and decryption in every api call. And I can't do it. I figured out what to use https://pub.dev/packages/js but it's too complicated for me. Maybe there is a good example with code?
#JS()
library web.js;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:medlike/app.dart';
import 'package:js/js.dart';
#JS()
#anonymous
abstract class WebBridge {
external void constructor();
external factory WebBridge();
external void addGlobalListener();
external void enableLogs();
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
runApp(App());
var bridge = WebBridge();
print(bridge.toString());
}
I managed to cope with this problem, although it was not easy. It was necessary to wrap the response in promiseToFeature and allowInterop
index.html
<script>
var bridge = window.webBridgeInstance;
async function sendBotEvent(eventObject) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('done!');
}, 1500);
setTimeout(() => {
reject('err rejected!');
}, 2500);
});
}
</script>
api_client.dart
#JS()
library main;
import 'dart:js' as js;
typedef Callback<T> = dynamic Function(T arg);
#JS()
class Promise<T> {
external Promise<T> then(Callback<T> successCallback,
[Function errorCallback]);
external Promise<T> catchIt(Function errorCallback);
}
#JS('sendBotEvent')
external Promise<dynamic> sendBotEvent(Object objectParams);
class SmartAppClient {
Future<String> get(String endpoint, Object params) async {
return await promiseToFuture(sendBotEvent({
'method': 'get_$endpoint',
'params': params,
}).then(js.allowInterop((data) {
print('SUCCESS: $data');
return data;
}), js.allowInterop((err) {
print('ERROR: $err');
return err;
})));
}
}
run:
void testSmartappFuncs() async {
await SmartAppClient()
.get(
'urlString',
{},
)
.then((value) {
print('RESULT RESULT RESULT');
print(value);
})
.catchError((onError) {
print('ERROR ERROR ERROR');
print(onError);
});
}

Unable to implement singleton patten in javascript

I am trying to implement a singleton pattern for the fastify instance. My code is as follows :-
const { createFastifyServer: server } = require("../app");
const getFastifyInstance = (() => {
let fastify;
return {
fastifyInstance: async () => {
if (!fastify) {
console.log("Called")
fastify = server();
await fastify.ready();
}
return fastify
}
}
})();
const { fastifyInstance } = getFastifyInstance
module.exports = fastifyInstance
Now wherever I am importing the code in a different file, the console prints "Called" each time it's imported in a new file, but shouldn't that be only once if singleton pattern was correctly implemented. Any idea what am I doing wrong?

Angular Transfer State not preventing repeat http calls

I have the http request being made a service which is injected onto my component and subscribed to from there. Since I introduced server side rendering with angular universal to my application, the results on the page are repeated at least twice.
I have method which is called on click, which performs the http request to facebook's api
getAlbum(albumId: number) {
this.albumPhotos = this.state.get(ALBUM_PHOTOS_KEY, null as any);
if (!this.albumPhotos) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.bachataPicsArray = res;
this.state.set(ALBUM_PHOTOS_KEY, res as any);
});
}
}
I declared the const variable below the imports
const ALBUM_PHOTOS_KEY = makeStateKey('albumPhotos');
And I also declared the property
albumNames: any;
I am assuming I have done all of the imports right I have the code on github in the gallery component.
You are on the right pass, you just need to handle your service differently if you are on the server or the browser side to perform your queries only once and not twice.
Pseudo logic:
If server -> Do http request -> Set value in transfer-state
If browser -> Get value from transfer-state
To do so, you could for example enhance your Service like following:
#Injectable()
export class FacebookEventsService {
const ALBUM_PHOTOS_KEY: StateKey<number>;
constructor(#Inject(PLATFORM_ID) private platformId: Object, private http: HttpClient) {
this.ALBUM_PHOTOS_KEY = makeStateKey('albumPhotos');
}
getBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
// Here we check if server or browser side
if (isPlatformServer(this.platformId)) {
return this.getServerBachaDiffFacebookEvents();
} else {
return this.getBrowserBachaDiffFacebookEvents();
}
}
getServerBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
return this.http.get(this.facebookEventsUrl)
.map(res => {
// Here save also result in transfer-state
this.transferState.set(ALBUM_PHOTOS_KEY, calendarEvents);
});
}
getBrowserBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
return new Observable(observer => {
observer.next(this.transferState.get(ALBUM_PHOTOS_KEY, null));
});
}
}
UPDATE
To use this logic you would also need:
TransferHttpCacheModule (to be initialized in app.module.ts).
TransferHttpCacheModule installs a Http interceptor that avoids
duplicate HttpClient requests on the client, for requests that were
already made when the application was rendered on the server side.
https://github.com/angular/universal/tree/master/modules/common
ServerTransferStateModule on the server side and BrowserTransferStateModule on the client side to use TransferState
https://angular.io/api/platform-browser/TransferState
P.S.: Note that if you do so and enhance your server, of course you would not need anymore to set the value in transfer-state in your getAlbum() method you displayed above
UPDATE 2
If you want to handle the server and browser side as you did in your gallery.component.ts, you could do something like the following:
getAlbum(albumId: number) {
if (isPlatformServer(this.platformId)) {
if (!this.albumPhotos) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.bachataPicsArray = res;
this.state.set(ALBUM_PHOTOS_KEY, null);
});
}
} else {
this.albumPhotos = this.state.get(ALBUM_PHOTOS_KEY,null);
}
}
UPDATE 3
The thing is, your action getAlbum is never called on the server side. This action is only used on the browser side, once the page is rendered, when the user click on a specific action. Therefore, using transfer-state in that specific case isn't correct/needed.
Furthermore not sure that the Observable in your service was correctly subscribed.
Here what to change to make it running:
gallery.component.ts
getAlbum(albumId: number) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.albumPhotos = res;
});
}
facebook-events.service.ts
getBachadiffAlbumPhotos(albumId: number): Observable<Object> {
this.albumId = albumId;
this.facebookAlbumPhotosUrl = `https://graph.facebook.com/v2.11/${this.albumId}/photos?limit=20&fields=images,id,link,height,width&access_token=${this.accessToken}`;
return Observable.fromPromise(this.getPromiseBachaDiffAlbumPhotos(albumId));
}
private getPromiseBachaDiffAlbumPhotos(albumId: number): Promise<{}> {
return new Promise((resolve, reject) => {
this.facebookAlbumPhotosUrl = `https://graph.facebook.com/v2.11/${this.albumId}/photos?limit=20&fields=images,id,link,height,width&access_token=${this.accessToken}`;
let facebookPhotos: FacebookPhoto[] = new Array();
let facebookPhoto: FacebookPhoto;
const params: HttpParams = new HttpParams();
this.http.get(this.facebookAlbumPhotosUrl, {params: params})
.subscribe(res => {
let facebookPhotoData = res['data'];
for (let photo of facebookPhotoData) {
facebookPhotos.push(
facebookPhoto = {
id: photo.id,
image: photo.images[3].source,
link: photo.link,
height: photo.height,
width: photo.width
});
}
resolve(facebookPhotos);
}, (error) => {
reject(error);
});
});
}
UPDATE 4
ngOnInit is executed on the server side, this means that my very first answer here has to be use in this case.
Furthermore, also note that on the server side you doesn't have access to the window, therefore calling $
With gallery.component.ts you could do something like this to run only the http queries once but this won't solve all your problems, I think it will still need further improvements.
ngOnInit() {
if (isPlatformServer(this.platformId)) {
this.facebookService.getBachadiffFacebookVideos().subscribe(res => {
this.bachataVidsArray = res;
this.state.set(VIDEOS_KEY, res as any);
});
this.facebookService.getBachadiffFacebookLastClassPictures().subscribe(res => {
this.bachataPicsArray = res;
this.state.set(LAST_CLASS_PICTURES_KEY, res as any);
});
this.facebookService.getBachadiffAlbumNames().subscribe(res => {
this.bachataAlbumHeaderNames = res;
this.state.set(ALBUM_NAMES_KEY, res as any);
});
} else {
$('ul.tabs').tabs();
this.bachataVidsArray = this.state.get(VIDEOS_KEY, null as any);
this.bachataPicsArray = this.state.get(LAST_CLASS_PICTURES_KEY, null as any);
this.bachataAlbumHeaderNames = this.state.get(ALBUM_NAMES_KEY, null as any);
}
}

Angular 2 mock Http get() to return local json file

What is the easiest way to mock the response returned by Http get() in Angular 2?
I have local data.json file in my working directory, and I want get() to return response containing that data as a payload, simulating the rest api.
Documents for configuring the Backend object for Http seemed somewhat obscure and overcomplicated for such a simple task.
You need to override the XhrBackend provider with the MockBackend one. You need then to create another injector to be able to execute a true HTTP request.
Here is a sample:
beforeEachProviders(() => {
return [
HTTP_PROVIDERS,
provide(XHRBackend, { useClass: MockBackend }),
SomeHttpService
];
});
it('Should something', inject([XHRBackend, SomeHttpService], (mockBackend, httpService) => {
mockBackend.connections.subscribe(
(connection: MockConnection) => {
var injector = ReflectiveInjector.resolveAndCreate([
HTTP_PROVIDERS
]);
var http = injector.get(Http);
http.get('data.json').map(res => res.json()).subscribe(data) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: data
})));
});
});
}));
By the way, you need to mock the XHRBackend and provide mocked data in a class with the createDb method. createDb method returns the mocked JSON object. To load that data provide correct URL to http.get, for example, if JSON object is contained in a variable mockedObject, then the URL should be "app\mockedObject".
You can read more details here: https://angular.io/docs/ts/latest/guide/server-communication.html.
You can use the HttpTestingController available via the core TestBed as to me it feels more intuitive (each to their own, of course). Untested snippet:
import { TestBed, async } from '#angular/core/testing';
import { HttpTestingController } from '#angular/common/http/testing';
import { MyApiService } from './my-api.service';
export function main() {
describe('Test set', () => {
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
providers: [MyApiService]
});
httpMock = TestBed.get(HttpTestingController);
});
it('should call get', async(() => {
const data: any = {mydata: 'test'};
let actualResponse: any = null;
MyApiService.get().subscribe((response: any) => {
actualResponse = response;
});
httpMock.expectOne('localhost:5555/my-path').flush(data);
expect(actualResponse).toEqual(data);
}));
});
}

Webpack shows parent scope as undefined

EDIT: I have just gone through the process of switching back to browserify and am having the same problem. So no longer a webpack problem. Still need help though
I am in the process of switching from broswerify to webpack. I have created an abstraction for my ajax calls. In that file I have some private variables that I use to set URL and timeout etc. For some reason it shows these variables (and the entire 'closure') as undefined, leading to some weird bugs. This code was working perfectly fine with browserify.
This is my webpack.config.js
const webpack = require('webpack');
const path = require('path');
module.exports = {
devtool: 'source-map',
entry: path.resolve(__dirname, 'src', 'client', 'index.js'),
output: {
path: path.resolve(__dirname, 'public'),
publicPath: 'localhost:3002',
filename: 'bundle.js',
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/,
},
{
test: /\.(png|jpg)$/,
loader: 'url-loader',
},
],
},
plugins: [
new webpack.DefinePlugin({
__API_URL__: JSON.stringify('http://localhost:3002/api'),
}),
],
};
This is my api wrapper api.js
import request from 'superagent';
import store from './store';
import { system, account } from '../core/actions';
const API_URL = __API_URL__;
const TIMEOUT = 10000;
const _pendingRequests = {};
function getJwt() {
/**
* This retrieves the JSON Web Token from local or session storage
* We simply try both so that we don't have to subscribe to the store
* and make sure some flag is constantly updated. The reducer that handles
* the successful login will place the token in the proper place.
*/
let token = localStorage.getItem('JWT_TOKEN');
if (token) {
return 'Bearer ' + token;
}
token = sessionStorage.getItem('JWT_TOKEN');
if (token) {
return 'Bearer ' + token;
}
return null;
}
function addRequest(key, pendingRequest) {
_pendingRequests[key] = pendingRequest;
}
function abortPendingRequests(key) {
if (_pendingRequests.hasOwnProperty(key)) {
_pendingRequests[key]._callback = () => {
};
_pendingRequests[key].abort();
_pendingRequests[key] = null;
}
}
function digest(resolve, reject) {
return function consume(err, res) {
if (err && err.timeout === TIMEOUT) {
return store.dispatch(system.apiTimeout());
} else if (res.status === 401) {
return store.dispatch(account.logout());
} else if (!res.ok) {
return reject(res);
} else {
if (err) {
return reject(err);
} else {
return resolve(res.body);
}
}
};
}
export function get(actionType, resource) {
// abortPendingRequests(actionType);
return new Promise((resolve, reject) => {
const jwt = getJwt();
const url = `${API_URL}${resource}`;
const requested = request
.get(url)
.timeout(TIMEOUT);
if (jwt) {
requested.set('Authorization', jwt);
}
// addRequest(actionType, requested);
requested.end(digest(resolve, reject));
});
}
export function post(actionType, resource, data) {
// abortPendingRequests(actionType);
return new Promise((resolve, reject) => {
const jwt = getJwt();
const url = `${API_URL}${resource}`;
const requested = request
.post(url)
.timeout(TIMEOUT);
if (jwt) {
requested.set('Authorization', jwt);
}
if (data) {
requested.send(data);
}
// addRequest(actionType, requested);
requested.end(digest(resolve, reject));
});
}
export function put(actionType, resource, data) {
// abortPendingRequests(actionType);
return new Promise((resolve, reject) => {
const jwt = getJwt();
const url = `${API_URL}${resource}`;
const requested = request
.put(url)
.timeout(TIMEOUT);
if (jwt) {
requested.set('Authorization', jwt);
}
if (data) {
requested.send(data);
}
requested.end(digest(resolve, reject));
});
}
export function del(actionType, resource) {
// abortPendingRequests(actionType);
return new Promise((resolve, reject) => {
const jwt = getJwt();
const url = `${API_URL}${resource}`;
const requested = request
.del(url)
.timeout(TIMEOUT);
if (jwt) {
requested.set('Authorization', jwt);
}
// addRequest(actionType, requested);
requested.end(digest(resolve, reject));
});
}
There are some weird comments that are the result of trying to debug the problem. But basically, if I set a breakpoint at const _pendingRequests = {}; it shows API_URL and TIMEOUT as being set properly. But if I set a breakpoint at const url =${API_URL}${resource}; in export function get it shows them as undefined as I will show with screenshots.
One thing I am just noticing is that it is breaking on the child scope prior to breaking on the parent scope. I am guessing that has something to do with it, but I am not sure how to change this behavior. I work in node so I have written this like I would write it for the server.
This is the file where I am importing api.js
import * as api from '../../core/api';
import { endpoints } from '../../constants';
export const FETCH_LOOKUPS = 'FETCH_LOOKUPS';
export const FETCH_LOOKUPS_SUCCESS = 'FETCH_LOOKUPS_SUCCESS';
export function fetchLookupsSuccess(lookups) {
return {
type: FETCH_LOOKUPS_SUCCESS,
lookups,
};
}
export function asyncFetchLookups() {
return dispatch => {
return api.get(FETCH_LOOKUPS, endpoints.LOOKUP)
.then(lookups => dispatch(fetchLookupsSuccess(lookups)));
};
}
export const FETCH_LANG = 'FETCH_LANG';
export const FETCH_LANG_SUCCESS = 'FETCH_LANG_SUCCESS';
export function fetchLangSuccess(language) {
return {
type: FETCH_LANG_SUCCESS,
language,
};
}
export function asyncFetchLang() {
return dispatch => {
return api.get(FETCH_LANG, endpoints.LANGUAGE)
.then(language => dispatch(fetchLangSuccess(language)));
};
}
Started digging into the transpiled code and found this
function(module, exports, __webpack_require__) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TIMEOUT = exports.API_URL = undefined;
exports.get = get;
exports.post = post;
exports.put = put;
exports.del = del;
var _superagent = __webpack_require__(427);
var _superagent2 = _interopRequireDefault(_superagent);
var _store = __webpack_require__(430);
var _store2 = _interopRequireDefault(_store);
var _actions = __webpack_require__(444);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var API_URL = exports.API_URL = ("http://localhost:3002/api"); /**
* This file serves as a wrapper for any ajax requests that need to be made
* - contains a generic call for PUT, POST, GET and DELETE request
* - always attempts to append a JSON Web Token if present
* - keeps track of all pending requests and aborts identical requests
*/
var TIMEOUT = exports.TIMEOUT = 10000;
As you can see it initially sets TIMEOUT and API_URL as undefined. It then exports get, post etc and then sets TIMEOUT and API_URL but that is after the exported get is already being accessed. Not sure why it sets them to undefined or how to fix this behavior.
According to how you require (probably import) the get function babel may transpile first the get function and hand it over to node's require which then evals it.
You then don't have API_URL transpiled yet. This looks like an edge case.
Instead of using the ES6 export, just for this file, use module.exports and use node's require to import it to dismiss that kind of bug.
If this works, try importing instead of requireing (and the way around exporting instead of using module.exports) to narrow the bug.
Note: This is more a hint/workaround than a solution. You may provide the file from where you make the require, this might be useful for other people to answer you.
The problem had to do with the function being called prior the javascript finishing parsing I guess. (Still not sure why) The workaround I found was to attach my initialization function to the window object and call it in an IIFE at the body of my HTML.

Categories

Resources