I am writing application in Angular 8 (8.0.3) and I need to import external JS file which will hold a URL reference for API that can and will change. The problem I am facing is that changes I do into this file after I have compiled Angular app (with ng build --prod), changes are not being picked up inside Angular, but it keeps the data as it was when the app was built.
This is the external JS file:
export function apiUrl() {
return 'http://www.localhost.local/api/v1/';
}
The apiUrl() does return proper value, but if I change it, the updated value is not reflected inside Angular app.
I also created .d.ts. file:
export declare function apiUrl();
Imported the external JS file into index.html:
<script type="module" src="assets/js/api_url.js"></script>
Used it in angular service:
import {apiUrl} from '../../assets/js/api_url';
export class ApiService {
private api_url: string = apiUrl();
constructor(...) {
console.log(apiUrl());
}
}
I did NOT import this file in angular.json and even if I do, nothing changes.
So, how to create and then import an external JS file that after it changes, the Angular app does pick up it changes?
EDIT:
Do note that I do not need a live app reload when this file changes, but I need for the file to be loaded from server each time the Angular app starts.
EDIT 2:
To explain why I need this. The Angular app will be used on internal network to which I do not have access to. The client currently does not know the API URL and even if he would, it can change at any moment. That's the reason why I need this to work.
In that case you should use environments
You can find them in src/environments directory.
For local development you have environment.ts file, for production environment.prod.ts
Example environment.ts:
export const environment = {
production: false,
apiUrl: 'http://www.localhost.local/api/v1/'
};
environment.prod.ts:
export const environment = {
production: true,
apiUrl: 'http://www.producitonurl.com/api/v1/'
};
To use this apiUrl value you need to import environment file in typescript file like this:
You need to import environment.ts not any other environment file, because angular is replacing it while building application
import {environment} from '../environments/environment';
#Injectable({
providedIn: 'root'
})
export class MyApiService {
apiUrl = environment.apiUrl;
}
But in case you want to use method from <script type="module" src="assets/js/api_url.js"></script>, just use window object like this:
private api_url: string = (window as any).apiUrl();
Related
I installed a npm package which contains a javascript file, what I want to use. The js file name is all.js and contains this code:
import { initExtended } from './extended'
import Header from './components/header/header'
function initAll(options) {
// Set the options to an empty object by default if no options are passed.
options = typeof options !== 'undefined' ? options : {}
// Allow the user to initialise GOV.UK Frontend in only certain sections of the page
// Defaults to the entire document if nothing is set.
var scope = typeof options.scope !== 'undefined' ? options.scope : document
// Find first header module to enhance.
var $toggleButton = scope.querySelector('[data-module="govuk-header"]')
new Header($toggleButton).init()
initExtended(options)
}
export {
initAll,
Header
}
File all.js is located in node_modules.
When I tried to import it directly from index.html like:
<script type="module" src="node_modules/#id-sk/frontend/govuk/all.js"></script>
It is not working. Console error, file not found.
I also tried import it via angular.json:
"scripts": [
"./node_modules/#id-sk/frontend/govuk/all.js"
]
Also not working with error "Uncaught SyntaxError: Cannot use import statement outside a module (at scripts.js:15241:1)". The error refers to line:
import { initExtended } from './extended'
I also tried to import it in polyfills but I don't know how to call it.
As you are speaking about angular.json, I assume that you are working in an Angular application bootstrapped using the Angular CLI with default settings.
To be able to use this package #id-sk/frontend in your typescript files, you have to import it directly into your typescript file.
1. Import #id-sk/frontend in your TS files
// Import everything into a local variable
import * as govuk from '#id-sk/frontend';
// Import specific object
import { HeaderExtended } from '#id-sk/frontend';
2. Run ng serve
⚠ Spoil: It will lead to typings errors
3. Let's add or create typings
As #id-sk/frontend is not written in typescript, the compile doesn't know about the typings of this library.
Following this statement, you have two choices:
Find or contribute to DefinitelyTyped in order to create the typings of your package #id-sk/frontend
Create a local file typings.d.ts in your ./src folder to declare an empty module
declare module "#id-sk/frontend"
4. Kill & run ng serve again
Enjoy it!
Go further
You can add typings to your module in order to give you autocompletion on the provided objects of #id-sk/frontend.
``ts
declare module "#id-sk/frontend" {
export interface Options {
scope?: Document
}
export function initAll(options: Options): void;
}
I want to have a external configuration file (JSON) in my React based project. That is the ultimate outcome or when I deliver it (public folder and the bundle.js) my configuration file also should be given. The User should be able to change the configurations according to his or her wish and use my app. That is there without recompiling my code one should be able to use it. In other words configuration file should not bundle with my app.
The accepted answer may work. However, why make it so complicated?
Step#1.
Create a file Config.js, with content
var Configs = {
prop1 = "abc",
prop2 = "123"
}
Step#2. Load the file in index.html via script tag.
<div id='root'></div>
<script src="Config.js"></script>
<script src="dist/bundle.js"></script></body>
Step#3. Just access the setting directly within any React component.
class MyComponent extents Component {
render() {
//you can access it here if you want
let myprop1 = window.Configs.prop1;
return(){
<div>myprop2 is: {window.Configs.prop2}</div>
}
}
}
Step#4. Profit?
Does not require or need to involve webpack, webpack-externals, webpack-config, import Config from 'config', or any other BS.
Why it works? because we declared 'Configs' to be a prop of the window object, and loaded it globally.
Like Joseph Fehrman said without thinking only about the JSON, using JS worked for me. This is what I did.
I created a JS file called configurations.js which included my required configurations
var configs = {
"aUrl": "https://localhost:9090/",
"bUrl": "https://localhost:9445/"};
Then in the index.html I added it.
<body>
<div id='root'></div>
<script src="configurations.js"></script>
<script src="dist/bundle.js"></script></body>
Then in the webpack.config.js I added it to externals like this. (Note that in the configurations.js, name of the variable is configs).
externals: {
config: "configs",
}
Then in where ever I want it, I can import it and use it nicely. This worked perfectly where I was able to change the configurations after it was deployed (That is did not have to recompile the code where my bundle.js remained untouched :-)).
An example showing how it was used is given below.
import { Component } from 'react';
import axios from 'axios';
import Config from 'config';
/**
* #class GetProductAreas
* #extends {Component}
* #description Get ProductAreas
*/
class GetProductAreas extends Component {
/**
* #class GetProductAreas
* #extends {Component}
* #description get product areas
*/
getproductAreas() {
const url = Config.aUrl;
return axios.get(url).then((response) => {
return (response.data);
}).catch((error) => {
throw new Error(error);
});
}
}
export default (new GetProductAreas());
The question is a bit vague. I think I know what you are asking for. As long as you are planning on using Webpack or Browserify you could do the following. It does require slightly different thinking instead of a pure JSON file using a JS file to mask it.
config.js:
let config = {
option1: true,
option2: false
}
module.exports = config;
And then from your file using the configuration you could do something similar to the following.
app.js:
import React from 'react';
import ReactDOM from 'react-dom';
import config from './my/relative/config/path/config';
import MyOtherComponent from './components/my_component';
let component = (<MyOtherComponent config={config} />);
ReactDOM.render(component, document.querySelector('mount'));
Last solution worked great, here's some improvements:
Config file, in /public folder:
config.js
var Configs = {
var1: "value",
var2: "value2"
}
In /public/index.html file, add script call in the header
<head>
....
<script src="config.js"></script>
....
</head>
Last, call the var from the code. Works great!
import React from 'react'
....
const data = window.Configs.var1
With this solution I can have several servers without recompiling, and it's easy to do.
I have some external application that provide config.json for my Angular 2 application.
I need point of entry for my angular 2 like:
angular2Application.setConfig('../config.json'); // in browser console e.g.
(then i can use this json in any my service of angular2 app)
and also my app should be able to send some data from service to any external app by the calling some method like:
var config = angular2Application.getConfig(); // in external other JS application
Probably i should do external calls through global window ? Any help and advices or examples will appreciated.
My angular 2 app service which has global config to be shared.
"Another js app" - this means another js application in the same window.
import {Injectable} from "#angular/core";
import {Observable} from "rxjs";
import {SomeService} from "../../some.service";
#Injectable()
export class SharingService {
public config; // the external app should be able to get this config
constructor(public someService: SomeService) {
this.someService.dataChanged.subscribe(
(data) => {
this.config = data;
}
);
}
}
You can use something like socket.io and create a socket communication between two applications.
I am working on the front end of a file upload service. I am currently ignoring the service path with respect to the backend. I have run into a strange problem. I have a few generated components that sit within the app component. When I end the serve from console and do ng serve again, it errors out. It says:
The only way I have found to get rid of this is to erase my uploader service injection, save the file, then re-insert the injection. This is how it is supposed to look:
The only way to get ng serve to work is to by erasing the line private service: UploaderService
Any idea why this is happening? Am I missing something with my injection? My UploaderService is marked as Injectable() and the components that use it are under Directives.
Update:
What I found out is that it is unrelated to the UploaderService. I have a component that does not inject the UploaderService. I fix it the same way I fix the other components that inject the UploaderService. By deleting the parameters of the constructor, saving, and then putting the parameters back. Then it will serve
Update2:
The generated componenet, upload.component.t, has a spec file that is generated with it, upload.component.spec.ts
It has a error that asks for parameters like so:
My UploadComponent constructor has a parameter in it, where i inject the UploaderService. In the spec.ts file, a new UploadCompent is created, but does not contain any arguments. I am guessing this is where I am going wrong. How do I work around this?
Here is my UploaderService:
import { Injectable } from '#angular/core';
import {Http, Response, HTTP_PROVIDERS, Headers, HTTP_BINDINGS, RequestOptions} from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import { ItemEntryComponent } from './item-entry';
import { Query } from './Query';
#Injectable()
export class UploaderService {
public URL: string;
private query: Query;
public filesSelected: Array<ItemEntryComponent> = [];
progress$: any;
progress: any;
progressObserver: any;
//CONSTRUCTOR
constructor(private http: Http) {
//***SET URL***
this.URL = 'http://localhost:7547/api/picker';
//Create Query for url
this.query = new Query(this.URL);
//Create progress attribute
this.progress$ = Observable.create(observer => {
this.progressObserver = observer
}).share();
}
}
Problem solved!
I had not realized the generated files included a spec testing file, in my example it was upload.component.spec.ts. Getting rid of those files gets rid of the errors that ask for parameters to be filled in inside the test files and now ng serve works.
I will try to keep this as short as possible.
The problem I am having is an extension of this SO question
I am trying to pass javascript variable from the "page level" into a service in my angular 2 app. I followed the direction of Gunter in the answer in the above SO question.
I used an opaque token to capture page variable names and pass them into the app constructor. This works perfectly when I am in development, but once I try to bundle the app it stops working. I use gulp-jspm-build to bundle my app and I have mangle set to false to avoid some other errors.
My app lives inside a CMS and the cms pre-processes my apps's index.html and replaces certain tokens with values.
Here is the part of the index.html of my angular app that gets pre processed with token replacement:
<!-- 2. Capture values to pass to app -->
<script type="text/javascript">
var moduleId = parseInt("[ModuleContext:ModuleId]");
var portalId = parseInt("[ModuleContext:PortalId]");
var tabId = parseInt("[ModuleContext:TabId]");
var dnnSF = $.ServicesFramework(moduleId);
if ("[ModuleContext:EditMode]" === 'True') {
var editMode = true;
}
// console.log('editMode = ' + editMode);
</script>
<!-- 3. Replaces with actual path to ststem.config.js -->
[Javascript:{path: "~/my-app/systemjs.config.js"}]
<!-- 4. APP selector where is it rendered-->
<my-app>Loading...</my-app>
Notice the [ModuleContext:ModuleId] - this gets replaced with a number value that I need to use in the angularApp that gets bootstrapped on this page.
So my main.ts file looks like this:
import {bootstrap} from '#angular/platform-browser-dynamic';
import {provide} from '#angular/core';
import {AppComponent} from './app.component';
import {dnnModId, dnnSF, dnnPortalId} from './shared/dnn/app.token';
import {HashLocationStrategy, LocationStrategy} from '#angular/common';
import {ROUTER_PROVIDERS} from '#angular/router';
import {HTTP_PROVIDERS} from '#angular/http';
// declare
declare var $: any;
declare var moduleId: any;
declare var portalId: any;
// the providers & services bootstrapped in this root component
// should be available to the entire app
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(LocationStrategy, { useClass: HashLocationStrategy }),
provide(dnnModId, { useValue: moduleId }),
provide(dnnPortalId, { useValue: portalId }),
provide(dnnSF, { useValue: $.ServicesFramework(moduleId) })
]);
I added declare var moduleId: any; so that typescript does not throw compilation errors. But this part is lost when bundled.
Here is how I define my opaque tokens:
import {OpaqueToken} from '#angular/core';
// Opaque tokens create tokens that can be used in the Dependency Injection Provider
export let dnnModId: any = new OpaqueToken('moduleId');
export let dnnPortalId: any = new OpaqueToken('portalId');
export let dnnTabId: any = new OpaqueToken('tabId');
export let dnnSF: any = new OpaqueToken('sf');
MY ERROR
I get an error on the following line:
core_1.provide(app_token_1.dnnModId, { useValue: moduleId
In my bundled .js file for the app.
the error is
app.min.js Uncaught ReferenceError: moduleId is not defined
QUESTION:
Can someone help me figure out why this works in development but not once I bundle my files together?
A huge thanks in advance
This turned out to be an issue with my CMS. My CMS took the javascript files and added them to the top of the page.
I had to change
[Javascript:{path: "~/my-app/systemjs.config.js"}]
to
<script src="/DesktopModules/regentsigns-app/systemjs.config.js"></script>
The top example is used by the CMS token replace function that parses the html page, it placed the bundled angular.min.js file above the selector and inline javascript that captured the global variables.
So by using the simple manual script tag import of the app.js files I fixed the load order issue.
In tsconfig.json, you should have
"module": "commonjs"
under
"compilerOptions"