How to run one angular app with two urls? - javascript

Wanted to know is there any way that we can run one angular 6 app on with two rendering URL ?
Example :
www.domain.com/abc1
www.domain.com/abc2
We have two urls that will be using the same application but the difference is whenever abc1 is rendered data shown is different then the abc2.
Both are sharing the same code/application.
Not sure how to do this or that's possible ?

If the data comes from backend and the Angular apps differ in datasource you should try this:
ng build --prod --base-href "/abc1/"
ng build --prod --base-href "/abc2/"

you can create different layouts based on the view you want to load.

An alternative approach to the others listed would be to actually use the same app for this, just on different routes.
Say you are listing Todos, but on /abc1 they com from server-1.com/api, and on /abc2, they come from server-2.com/api.
You'd first create your app routing module, to capture the "subdomain" (as you called it, although it's actually a virtual directory) part of the url. So, app-routing.module.ts has this for routes:
const routes = [
{ path: ':subdomain', component: TodosComponent },
];
// and the rest of it.
(For simplicity I'm not doing modules here).
So now your TodosComponent simply reads the "subdomain" and fetches stuff from proper backend:
class TodosComponent implements OnInit {
// inject the backend service (or more) and activated route
constructor(private backend: BackendService,
private route: ActivatedRoute) {
}
// on init, get that route param
ngOnInit() {
this.route.params.subscribe(params => {
// catch the "subdomain" param
const subdomain = params.subdomain;
// now you'd move this to a service, but here:
if (subdomain === 'abc1') {
this.backend.get('http://server-1.com/api').subscribe(...);
} else if (subdomain === 'abc2') {
this.backend.get('http://server-2.com/api').subscribe(...);
} else {
// add more as needed here.
}
})
}
}
Now, this would be the same app that would work completely as a SPA as you switch between subdomains.
In the actual app, your app-routing would pass this down to a (lazy) module, this module would have all the components, services etc built so that you can parametrize all the setup with this url segment/route parameter.

Related

NextJS use getServerSideProps with a URL sub path

I've been using the solution at NextJS deploy to a specific URL path (and Deploy your NextJS Application on a different base path (i.e. not root)
) for a year now on our project. Our next.js server is hosted on another website under a sub-path, but the sub-path comes from the other server, not ours. I.e. we're at http://example.com/project/path. But our server is running at http://localhost:8081/ so we can't use the basePath since that changes our local path. This has been working great. We use a wrapped next/link for our links and just prepend our own basePath to API calls in most cases.
I recently tried switching to use getServerSideProps() rather than getInitialProps() since I was reading online that is the new preferred way (a single API call rather than the multiple we were making before). The problem I'm having is that I can't find a way to tell next.js that it needs to add the basePath to the getServerSideProps API calls it makes. We are using assetPrefix to point our images and css at the sub-path but it looks like there was a bug https://github.com/vercel/next.js/issues/15563 which was fixed that let us use assetPrefix rather than basePath.
Again, we can't use basePath since it changes localhost to http://localhost/project/path and it's the external website that has the extra pathing. Is there any other solution? Or are we just stuck on getIntialProps since it works and hope we don't lose that option in the future?
Currently, server-side rendering works, but when I navigate client-side, it returns a 404 on the getServerSideProps API call and the browser than redirects and loads the page server-side like:
Chrome network load. While it "works", it's not very performant since it must server-side render every page.
Edit: The proposed solution https://stackoverflow.com/a/66642225/1870780 does not work since we cannot use a basePath since it changes the local server base, not a remote server path pointing to our server.
Thank you #juliomalves for pointing me in the direction of the solution. The discussion at https://github.com/vercel/next.js/discussions/25681#discussioncomment-2026813 has a potential solution which works. In-lining the solution here to save others time, but credit goes to alizeait for the solution. Main difference between my solution an his, is his namespace does not include a leading / while mine does.
Typescript solution:
import { AppProps } from "next/app";
import { Router } from "next/router";
import { useEffect } from "react";
const useInterceptNextDataHref = ({
router,
namespace
}: {
router: Router;
namespace: string;
}) => {
useEffect(() => {
if (router.pageLoader?.getDataHref) {
const originalGetDataHref = router.pageLoader.getDataHref;
router.pageLoader.getDataHref = function (args: {
href: string;
asPath: string;
ssg?: boolean;
rsc?: boolean;
locale?: string | false;
}) {
const r = originalGetDataHref.call(router.pageLoader, args);
return r && r.startsWith("/_next/data")
? `${namespace}${r}`
: r;
};
}
}, [router, namespace]);
};
export default function MyApp ({ Component, pageProps, router }: AppProps) {
useInterceptNextDataHref({
router,
namespace: "/my-base-path"
});
return <Component {...pageProps} />;
}

Nuxtjs custom module

I'm quite new to Nuxtjs so I made a test project which purpose is merely the (of course) testing of Nuxtjs functionalities.
Currently I'm trying to create a simple custom module: afaik a module is basically a wrapper around a vou/js library/plugin, something like a high-level integration used to expose configurations on how the underlying library/plugin is imported and used in the Nuxt application.
So I'm trying with a simple module that declare some plain js classes that I'll use in my application, e.g. Order and Product, and that's what I came out with:
Directory structure
pages
the-page.vue
modules
classes
index.js
order.js
/modules/classes/index.js
const path = require('path')
export default function (moduleOptions) {
const { nuxt } = this
// add the debug plugin
this.addPlugin({
src: path.resolve(__dirname, 'order.js'),
})
}
/modules/classes/order.js
class Order {
constructor(id) {
this.id = id;
console.log('created order #' + this.id);
}
}
export {Order};
/nuxt.config.js
export default {
// ...
buildModules: [
// ...
'~/modules/classes'
],
// ...
}
/pages/the-page.vue
<script>
export default {
name: 'ThePage',
data () {
return {
}
},
methods: {
createOrder () {
const order = new Order(123)
}
}
}
</script>
The error
My defined class are still not imported in my pages:
/app/pages/the-page.vue
18:13 error 'order' is assigned a value but never used no-unused-vars
18:25 error 'Order' is not defined no-undef
Considerations
Probably I'm missing something about modules usage and/or implementation, but every tutorial I found starts with too complex scenarios, and since I'm at the beginning with Nuxtjs I need something easier to implement.
Ok, I found out that I was mistaken how NuxtJs modules are intended to work and was traying to do somenthing they are not intended for.
Nuxt modules cannot import js classes in every component of the application as I wanted to do, they just "add a property" to the main application instance that is made accessible through this.$<something>, like e.g. you can already do in simple Vue with the Vue Router or the Vuex store plugins that give access to the this.$router and this.$store properties.
NuxtJs modules just wrap simple plugins and expose configuration options to made.

Wrong module loaded on webpack federation with angular

I have 3 apps on ports 4200, 4201 and 4202.
On my first app (on 4200) I have a route to other apps which loads their corresponding modules, a ProfileModule, and I'm creating components based on this module's exported components.
Note that my apps have the same module and exported component names.
This works perfectly but for some reason, when I change my route from an app to the other one, when components have already been loaded once, webpack retrieves the wrong module.
What I'm doing here is:
I'm routing to my second app (which is on 4201), it loads my ProfileModule from second-app
I'm routing to my third app (which is on 4202), it loads my ProfileModule from third-app
I'm going back to my second-app and it loads my ProfileModule from third-app instead of second-app
I guess it is due to the module names, but shouldn't it work if it's on a different remote?
Here is how I get my modules:
async function lookupExposedModule<T>(
remoteName: string,
exposedModule: string
): Promise<T> {
// Initializes the share scope. This fills it with known provided modules from this build and all remotes
await __webpack_init_sharing__("default");
const container = window[remoteName] as Container; // or get the container somewhere else
// Initialize the container, it may provide shared modules
await container.init(__webpack_share_scopes__.default);
const factory = await container.get(exposedModule);
const Module = factory();
return Module as T;
}
I found a solution here.
But in my case, I had the same names for different remotes in my webpack config.

Angular 2 Shared Data Service is not working

I have built a shared data service that's designed to hold the users login details which can then be used to display the username on the header, but I cant get it to work.
Here's my (abbreviated) code:
// Shared Service
#Injectable()
export class SharedDataService {
// Observable string source
private dataSource = new Subject<any>();
// Observable string stream
data$ = this.dataSource.asObservable();
// Service message commands
insertData(data: Object) {
this.dataSource.next(data)
}
}
...
// Login component
import { SharedDataService } from 'shared-data.service';
#Component({
providers: [SharedDataService]
})
export class loginComponent {
constructor(private sharedData: SharedDataService) {}
onLoginSubmit() {
// Login stuff
this.authService.login(loginInfo).subscribe(data => {
this.sharedData.insertData({'name':'TEST'});
}
}
}
...
// Header component
import { SharedDataService } from 'shared-data.service';
#Component({
providers: [SharedDataService]
})
export class headerComponent implements OnInit {
greeting: string;
constructor(private sharedData: SharedDataService) {}
ngOnInit() {
this.sharedData.data$.subscribe(data => {
console.log('onInit',data)
this.greeting = data.name
});
}
}
I can add a console log in the service insertData() method which shoes the model being updated, but the OnInit method doesn't reflect the change.
The code I've written is very much inspired by this plunkr which does work, so I am at a loss as to what's wrong.
Before posting here I tried a few other attempts. This one and this one again both work on the demo, but not in my app.
I'm using Angular 2.4.8.
Looking through different tutorials and forum posts all show similar examples of how to get a shared service working, so I guess I am doing something wrong. I'm fairly new to building with Angular 2 coming from an AngularJS background and this is the first thing that has me truly stuck.
Thanks
This seems to be a recurring problem in understanding Angular's dependency injection.
The basic issue is in how you are configuring the providers of your service.
The short version:
Always configure your providers at the NgModule level UNLESS you want a separate instance for a specific component. Only then do you add it to the providers array of the component that you want the separate instance of.
The long version:
Angular's new dependency injection system allows for you to have multiple instances of services if you so which (which is in contrast to AngularJS i.e. Angular 1 which ONLY allowed singletons). If you configure the provider for your service at the NgModule level, you'll get a singleton of your service that is shared by all components/services etc. But, if you configure a component to also have a provider, then that component (and all its subcomponents) will get a different instance of the service that they can all share. This option allows for some powerful options if you so require.
That's the basic model. It, is of course, not quite so simple, but that basic rule of configuring your providers at the NgModule level by default unless you explicitly want a different instance for a specific component will carry you far.
And when you want to dive deeper, check out the official Angular docs
Also note that lazy loading complicates this basic rule as well, so again, check the docs.
EDIT:
So for your specific situation,
#Component({
providers: [SharedDataService] <--- remove this line from both of your components, and add that line to your NgModule configuration instead
})
Add it in #NgModule.providers array of your AppModule:
if you add it in #Component.providers array then you are limiting the scope of SharedDataService instance to that component and its children.
in other words each component has its own injector which means that headerComponentwill make its own instance of SharedDataServiceand loginComponent will make its own instance.
My case is that I forget to configure my imports to add HttpClientModule in #NgModules, it works.

Configure Angular 2 HTTP service

I wonder what is the right way to use http service. For example all my requests to server starts with /Api.
Should I write this.http.get('/Api/SomeRoute/:id') every time, or more elegant way exists to omit Api?
Or should I create some other managerService which will use http?
Something like an endpoint url is probably a good example for a general configuration file.
In my angular 2 apps i use the dependency injection for it. In this case i have something like an app.config.ts using OpaqueToken to make it injectable:
import { OpaqueToken } from '#angular/core';
export interface IAppConfig {
appTitle: string;
endpointUrl: string;
};
export const CONFIG: IAppConfig = {
appTitle: 'MyApp',
endpointUrl: 'http://api.myrealservice.com/'
};
export const LOCALCONFIG: IAppConfig = {
appTitle: 'MyApp (LOCAL)',
endpointUrl: 'http://localhost:8080/api/'
};
export let APP_CONFIG = new OpaqueToken('app.config');
This way you can have several configs (for example for local development or production etc..) and define this as a provider in your module like this:
providers: [
...,
{
provide: APP_CONFIG,
useValue: CONFIG
},
...
]
Then i just inject this config wherever i may need it, for example in a backend service to use the endpointUrl:
...
constructor(#Inject(APP_CONFIG) private _config:Config) {
console.log(this._config.endpointUrl);
}
In your module you can just change the kind of Config you want to provide (in this example CONFIG or LOCALCONFIG for example) and don't have to change it anywhere else anymore.
Hope this helps.
After your edit, you added a second question Or should I create some other managerService which will use http?:
Short answer: yes. You should seperate logic of your components, services, etc. as much as possible.
Let's say you have a an API which serves information about cats and dogs. What you want to have in your frontend would probably be a CatService, a DogService and a BackendService or ApiService, whatever you want to call it.
The CatService and DogService are the ones your view components will interact with, for example they will have methods like getCatById() or something like that. These services will most likely interact with anohter service, the BackendService which will inject Http and use general methods like post(), get() and so on.
So your BackendService is the one who has to know the specific urls, handle the responses or errors and report back to the calling services with the results, while the other ones will just be used as a pipeline and handle specific business logic.
You are right, it is better to put repeating values in a variable. You can put the base URL in a const:
const apiUrl = '/Api/';
and then if there is a change to the URL, you only change it in one place, and also your code looks cleaner. The usage is like this:
this.http.get(apiUrl + 'SomeRoute/:id')
Of course you can also create a function that does that for you, but that may be too much abstraction.

Categories