ngrx/data: best way to have multiple api roots? - ngrx

I would need to target different api endpoint roots from different angular modules.
For example
http://server:port/user-api from User module
http://server:port/admin-api from Admin module
http://server:port/checkout-api from Cart module
and so on.
From what I see, the api root called by ngrx/data can be changed but only globally, by means of DefaultDataServiceConfig in app.module
How can I have different api roots in different Angular modules?
At the moment I am using a CustomizeHttpUrlGenerator to change the urls based on a naming convention of mine, but I guess there's a better way.
Thanks

create a provider and change it when you want.
const API_ROOT = new InjectionToken<string>('BASE_HREF', {
factory: () => 'http://server:port/user-api ',
})
then for admin
...
providers: [
{
provide: API_ROOT,
useValue: 'http://server:port/admin-api',
}
],
...
and somewhere in code
constructor(#Inject(API_ROOT) public readonly apiRoot: string) {}

You can add an entry per Entity in the entityResource method of the HttpUrlGenerator.
Hence in the User module for the User entity you would have
#Injectable()
export class UserDataService extends DefaultDataService<User> {
constructor(http: HttpClient, httpUrlGenerator: HttpUrlGenerator) {
httpUrlGenerator.entityResource('User','http://server:port/user-api' );
super('User', http, httpUrlGenerator);
}
}

Similarly to malc answer, I suggest you override the config of the DataService on a case by case scenario.
export class UserDataService extends DefaultDataService<User> {
constructor(http: HttpClient, httpUrlGenerator: HttpUrlGenerator, defaultConfig: DefaultDataServiceConfig) {
super('User', http, httpUrlGenerator, { ...defaultConfig, root: 'http://server:port/user-api' })
}
}
This is less hacky than preemptively setting the knownHttpUrls of the DefaultDataService.

Related

How to use the same slug for different routes in Next.js? [duplicate]

I have quite a lot of routes defined and one of the routes is dedicated to user profiles.
Each user has a public profile accessible from HTTP://example.com/#username.
I have tried creating file pages/#[username].js but it doesn't seem to work.
Is there a way to have this behavior without passing # sign with the username because this would greatly complicate index.js handling homepage and I would like to have that code separated.
You can now do this like so in next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/#:username',
destination: '/users/:username'
}
]
}
}
This will make any link to /#username go to the /users/[username] file, even though the address bar will show /#username.
Then, in your /pages/[username].tsx file:
import { useRouter } from 'next/router'
export default function UserPage() {
const { query = {} } = useRouter()
return <div>User name is {query.username || 'missing'}</div>
}
Next.js does not support this yet.
You should watch this issue.

Adding prefix to Nextjs dynamic route

I have quite a lot of routes defined and one of the routes is dedicated to user profiles.
Each user has a public profile accessible from HTTP://example.com/#username.
I have tried creating file pages/#[username].js but it doesn't seem to work.
Is there a way to have this behavior without passing # sign with the username because this would greatly complicate index.js handling homepage and I would like to have that code separated.
You can now do this like so in next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/#:username',
destination: '/users/:username'
}
]
}
}
This will make any link to /#username go to the /users/[username] file, even though the address bar will show /#username.
Then, in your /pages/[username].tsx file:
import { useRouter } from 'next/router'
export default function UserPage() {
const { query = {} } = useRouter()
return <div>User name is {query.username || 'missing'}</div>
}
Next.js does not support this yet.
You should watch this issue.

How to use adal.js to authenticate an SPA in an iframe of another SPA (In the same AAD tenant)

I have an HTML application written in Angular JS, and I would like to allow other trusted internal developers to extend the application by creating their own applications in Angular hosted on different endpoints. The extensions want to be embedded inside the main shell by the use of <iframe>
These applications all exist in the same AAD Tenant, as different application registrations.
We have tried to set the iframe src="http://localhost:4200", and inside the inner application have used adal.js to authenticate against the AD. This inner application works fine when hosted directly in the browser, but when embedded in the iframe causes:
Refused to display 'https://login.microsoftonline.com/tenantid/oauth2/authorize?response_type=id_token&client_id=applicationId&redirect_uri=http%3A%2F%2Flocalhost%3A4200%2Fhome&state=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxab&client-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx&x-client-SKU=Js&x-client-Ver=1.0.17&nonce=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx' in a frame because it set 'X-Frame-Options' to 'deny'
Is what we are attempting to do sensible, or is this approach considered a security risk?
I have seen others solve this by also navigating to the child page, but then you lose the shell.
Here is the code from the AngularJS side:
var iframe = document.querySelector("#myiframe");
iframe.src = ENV.iframeURL + "/?data=" + $routeParams.data;
And, the code at the Angular Side is more complex:
Readme.md
I have copied in the readme.md file from the Angular application, so you can to get an insight on what we have done Angular side. Angular App works fine when it is the root of the web page.
Application details
The Main application has the authService injected into it, and onInit calls the applicationInit()
export class AppComponent implements OnInit {
constructor(private authService: AuthService) {
}
ngOnInit() {
this.authService.applicationInit();
}
}
```
The Auth Service is the main part which you will use within your own projects.
Referring to How to load adal.js in webpack inside Angular 2 (Azure-AD)
for details on this workflow
/// <reference path="../../../node_modules/#types/adal/index.d.ts" />
import { Injectable } from '#angular/core';
import { environment } from '../../environments/environment';
import 'expose-loader?AuthenticationContext!../../../node_modules/adal-angular/lib/adal.js';
#Injectable()
export class AuthService {
private context: adal.AuthenticationContext = null;
constructor() {
let adalSettings = environment.adalSettings;
let createAuthContextFn: adal.AuthenticationContextStatic = AuthenticationContext;
this.context = new createAuthContextFn(adalSettings);
}
Environment file while is loaded - loads in the relevant tenant, and applicationId
export const environment = {
production: false,
adalSettings: {
tenant: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
clientId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
redirectUri: 'http://localhost:4200/home',
postLogoutRedirectUri:'http://localhost:4200',
expireOffsetSeconds: 300
}
};
The current User use the service's getUser method: this returns a promise to the user
getUser(): Promise<adal.User> {
var result = new Promise<adal.User>(
(resolve, reject) => {
this.context.getUser((err, user) => {
if (err)
resolve(null);
else
resolve(user);
});
});
return result;
}
## Routes into the application
const routes: Routes = [
{
path: 'login',
component: LoginRedirectComponent
},
{
path: 'home',
component: HomeComponent,
canActivate: [AuthGuard]
},
{
path: '',
pathMatch: 'full',
redirectTo: 'home'
}
];
//TODO add other client routes - e.g. the data which is being posted in
What are your recommendations? Is there a best practice we should be following?
Thanks in Advance

Firebase 3.0 + Ember 2.0: The Torii adapter must implement `open` for a session to be opened

I'm having an issue with facebook authentication with the torii adapter, the error being: 'The Torii adapter must implement open for a session to be opened'.
I've visited many tutorials, and tried all presented methods, but most of them are usually old ember code and none of them seem to actually work.
Current state: I am able to login, I get the facebook popup and I can authorize.
Using fiddler, I can also see the response from the API containing a JSON response with all credentials from the user I authenticated with.
In the firebase console, I can see the authorized user, reset its password, deny access,...
All this leads me to believe that it's 'merely' a front-end issue where I can't seem to establish a proper 'session' to work with.
My end goal would be to pick up the relevant user data and transfer it to my firebase backend as a 'user' entry, allowing for quick registration for site visitors, but I'll be more than glad to have an active session so I can work out the rest on my own.
As a front-end rookie (I normally code C#), Ember may not have been the best choice to get the hang it, but I'm this far now, I'm not about to let it all slide and pick up a different framework.
My code:
config/environment.js
firebase: {
apiKey: 'xxxxxxx',
authDomain: 'myappname.firebaseapp.com',
databaseURL: 'https://myappname.firebaseio.com',
storageBucket: 'myappname.appspot.com',
messagingSenderId: '1234567890'
},
torii: {
sessionServiceName: 'session'
}
torii-adapters/application.js (I've changed this so much, I can't even remember what the original code was, because none of what I change/add/delete here seems to do anything at all.)
import Ember from 'ember';
import ToriiFirebaseAdapter from 'emberfire/torii-adapters/firebase';
export default ToriiFirebaseAdapter.extend({
firebase: Ember.inject.service(),
});
routes/application.js
import Ember from 'ember';
export default Ember.Route.extend({
beforeModel: function() {
return this.get('session').fetch().catch(function() {
});
},
actions:{
login: function(provider) {
this.get('session').open('firebase', {
provider: provider,
}).then(function(data) {
console.log(data.currentUser);
});
},
logout: function() {
this.get('session').close().then(function() {
this.transitionTo('application');
}.bind(this));
}
}
});
application.hbs
<div class="container">
{{partial 'navbar'}}
<a {{action "signIn" "facebook"}} class="btn">{{fa-icon "facebook"}}</a>
<a {{action "signIn" "twitter"}} class="btn">{{fa-icon "twitter"}}</a>
<a {{action "signIn" "github"}} class="btn">{{fa-icon "github"}}</a>
<a {{action "signIn" "google"}} class="btn">{{fa-icon "google"}}</a>
{{outlet}}
</div>
EDIT 1
Above code is giving me alot more errors after restarting ember server. Is this the cause of my troubles ? All the changes that seemingly didn't change a thing, weren't registered until after a server restart ? If that's the case, I may have passed the correct solution about a hundred times already...
EDIT 2
Changed the code to reflect the actual issue. The previous code was screwed beyond measure, but I never realized it because it didn't pick up until after a server restart.
EDIT 3
Found and tried this, to no avail: https://stackoverflow.com/a/32079863/4309050
This is Lorem Ipsum Dolor's answer, but updated for Ember 3.16+
// Inside routes/application.js
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
export default class ApplicationRoute Route {
#service session;
async beforeModel() {
try {
return this.session.fetch();
} catch {}
}
}
Note that in Ember 3.16+, it is not recommended to add actions to your Route.
Instead, you can add them to a Controller or Component context:
import Component from '#glimmer/component';
import { inject as service } from '#ember/service';
import { action } from '#ember/object';
export default class LoginLogout extends Component {
#service session;
#service router;
#action
async login(provider) {
let data = await this.session.open('firebase', { provider });
console.log(data.currentUser);
}
#action
async logout() {
await this.session.close();
this.router.transitionTo('application');
}
}
Note the addition of the router service. The Router service is the way we interact with routing anywhere in our apps.
import ToriiFirebaseAdapter from 'emberfire/torii-adapters/firebase';
export default class MyAdapter extends ToriiFirebaseAdapter {
}
I had the same issue and I noticed that my torii-adapters/application.js located under the pods structure (because I use it). So I moved the torii-adapters folder to app folder and everything started to work.
NOTE: for Ember 3.16+ apps, here is the same code, but with updated syntax / patterns: https://stackoverflow.com/a/62500685/356849
The below is for Ember < 3.16, even though the code would work as 3.16+ as fully backwards compatible, but it's not always fun to write older code.
Try to inject service inside your application route and move the beforeModel outside of the actions hash,
// Inside routes/application.js
export default Ember.Route.extend({
session: Ember.inject.service(), // (1)
beforeModel: function() {
return this.get('session').fetch().catch(function() {});
},
actions:{
login: function(provider) {
this.get('session').open('firebase', {
provider: provider,
}).then(function(data) {
console.log(data.currentUser);
});
},
logout: function() {
this.get('session').close().then(function() {
this.transitionTo('application');
}.bind(this));
}
}
});
I have completed the same thing (Firebase Torii Auth) yesterday, try to follow the guide carefully. The only thing missing from the guide is to inject session service manually.
Still remember the session you declared inside environment.js file? You have to inject it to make it available
session: Ember.inject.service(), // (1)
https://github.com/firebase/emberfire/blob/master/docs/guide/authentication.md
Inside my ToriiFirebaesAdapter,
import ToriiFirebaseAdapter from 'emberfire/torii-adapters/firebase';
export default ToriiFirebaseAdapter.extend({
});

Async load routes data and build route instruction for Angular 2

I try to build dynamically routes from angular2 (fetch route config from server), after that I parse it and generate instruction for component route (I have parent routes config and child into different components, because I don`t know how define route for child component into one main.app.ts file).
The problem is when app started and try to create routes config and routeGenerator is not build routes yet (async delay) cant parse routes data (because async delay, so routesData undefined now) and app is crashig. I dont know what to do with this. Looking for lifecycle hood (some like - #Angular2BeforeAppStarted ) but found nothing.
import {Component, Input, OnChanges} from 'angular2/core';
import {RouteConfig, RouterOutlet, ROUTER_DIRECTIVES, Router} from 'angular2/router';
/* ------- !Angular 2 native components ---------*/
import {routeGenInstance} from '../../config/routes/patient_routes';
protected const BUILT_MODULE_PATH: string = '/built/modules/patients/';
#Component({
template: `
<router-outlet ></router-outlet>
`,
directives: [RouterOutlet, ROUTER_DIRECTIVES]
})
#RouteConfig(routeGenInstance.getRouteDefinitions())
export class PatientsComponent {
#Input();
constructor() {}
}
Also i try to update routes in the same way (but app is crashed immediately because my Navigation link in navigation component is not have some correct link way)
import {RouteConfig, RouterOutlet, ROUTER_DIRECTIVES, Router} from 'angular2/router';
constructor(
private router: Router
) {
router.config([
routeGenInstance.getRoutesDefinition()
])
}
my route definitions use Async loader so they are correct and work whithout async delay. I don`t know how to make angular wait for my routes definitions and thet start to run the app.
Please, help me. Thanks.
UPD:
#Thierry many thanks for your help again. You are awesome my friend and mentor. One last question (last). Can you tell me how I can define routeConfig into one app file with child subrouting definition?
Its mean. I have main level routes into app files
{
path: '/',
name: 'Dashboard',
component: DashboardComponent,
useAsDefault: true
},
{
path: '/patients/...',
name: 'Patients',
component: PatientsComponent
},
and patient sub routes into patientsComponent (#RouteConfig)
{
path: '/', // root is appRoot/patients/...
name: 'PatientsList', component...},
{
"name": "Chart",
"path": "/chart/:id", component...
},
How to define this route config only into one app.file ? (How to configure route with sub routing in one file)?
An option could be to get your configuration before bootstrapping your application.
var injector = Injector.resolveAndCreate([HTTP_PROVIDERS]);
var http = injector.get(Http);
http.get('routes.json').map(res => res.json())
.subscribe(data => {
bootstrap(AppComponent, [
HTTP_PROVIDERS
provide('routesConfig', { useValue: data })
]);
});
Then you can have access the routes configuration by dependency injection and in a synchronous way:
#Component({
(...)
})
export class AppComponent {
constructor(#Inject('routesConfig') private routesConfig, private router:Router) {
// Configure here your routes
}
}
These two questions could help you:
How to bootstrap an Angular 2 application asynchronously
angular2 bootstrap with data from ajax call(s)
Edit
You can leverage the Observable.forkJoin method to load your route configuration from different requests:
var injector = Injector.resolveAndCreate([HTTP_PROVIDERS]);
var http = injector.get(Http);
Observable.forkJoin([
http.get('mainRoutes.json'),
http.get('childRoutes.json')
])
.map(responses => {
return {
main: res[0].json(),
children: res[1].json()
};
})
.subscribe(data => {
bootstrap(AppComponent, [
HTTP_PROVIDERS
provide('routesConfig', { useValue: data })
]);
});
Edit1
I think that you could try something like that:
[
{
path: '/patients/...',
name: 'Patients',
component: PatientsComponent,
childRoutes: [
{
path: '/', // root is appRoot/patients/...
name: 'PatientsList', component...
},
(...)
]
}
]
But you need to split the content to get different elements according to the hints you want to handle:
one for the root:
[
{
path: '/patients/...',
name: 'Patients',
component: PatientsComponent
}
]
several for children. For example for patients:
[
{
path: '/', // root is appRoot/patients/...
name: 'PatientsList', component...
},
(...)
]
In the new router (>= RC.3) https://angular.io/docs/js/latest/api/router/index/Router-class.html#!#resetConfig-anchor resetConfig can be used
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, children: [
{ path: 'simple', component: SimpleCmp },
{ path: 'user/:name', component: UserCmp }
] }
]);
See also https://github.com/angular/angular/issues/9472#issuecomment-229230093
You can load components asynchronously by providing a SystemJsComponentResolver.
Right now, you can load routes asynchronously and imperatively update the configuration using resetConfig.
Once AppModules are in master, we will utilize those to implement async loading of subconfigs.
https://github.com/angular/angular/issues/11437#issuecomment-245995186 provides an RC.6 Plunker
Check this:
DynamicalAsyncRouter
https://github.com/Longfld/DynamicalAsyncRouter
Using Observables/Promises to provide route translations is not a reliable solution, hence the Angular router expects Route[] or Routes, but an HTTP request can only return an Observable/Promise.
The Angular app gets initialized, but the retrieval process of route translations still goes on using Observables/Promises.
As Thierry Templier said, to get your configuration before bootstrapping your application would solve the problem.
Also, check the #ngx-i18n-router/core on github.

Resources