Async load routes data and build route instruction for Angular 2 - asynchronous

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.

Related

Nuxt3 prerender all routes

Nuxt will prerender and generate payload for almost all routes.
However for dynamic pages I believe you need to specify inside nuxt.config
nitro: {
prerender: {
crawlLinks: false,
routes: [
// all my routes goes here
]
}
}
My problem is where to make the fetch for all my routes and how to import it to nuxt.config the result.

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

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.

Can anyone help implementing Nuxt.js Google Tag Manager with function based id

I installed and add this code to my nuxt.config.js and it works perfectly fine. (Link to package)
modules: [
['#nuxtjs/google-tag-manager', { id: 'GTM-XXXXXXX' }],
]
Now I am trying to implement instead of a static ID a function which will return an ID.
I tried to add this lines into my nuxt.config. js but it is not working. Obviously I have to put it somewhere else or so...
This is what I tried
nuxt.config.js
const code = '1234567'
id: () => {
return 'GTM-' + code
}
export default {
...
modules: [
['#nuxtjs/google-tag-manager', { id: id }],
]
...
}
What would be the correct way implementing this?
I would like to do something like that at the end.
modules: [
['#nuxtjs/google-tag-manager', {
id: ({ req }) => {
if (req.headers.referer == "exmple.com")
return 'GTM-156'
if (req.headers.referer == "exmple.it")
return 'GTM-24424'
if (req.headers.referer == "exmple.es")
return 'GTM-2424'
}
}]]
EDIT:
I solved my problem by rewriting the whole module. It is not possible to use this Module because it is loaded only on build time. I rewrote the module and moved the code into nuxtServerInit.
nuxtServerInit is called on each request (modules only onetime). In the request I asked from which domain the request is coming. Depending on the domain I add different google-tag-manager id's to the head and the plugin.
From package docs:
modules: [
['#nuxtjs/google-tag-manager', {
id: () => {
return axios.get('http://example.com/')
.then(({ data }) => {
return data.gtm_id
})
}
}]]
You can use process.env.NODE_ENV inside function which will return an ID
Edit 1
To put the gtm id, depending on req.headers.referer you need to provide context to the function returning the id. This can be done in middleware
See how it works here
https://github.com/nuxt-community/modules/blob/master/packages/google-tag-manager/plugin.js
Edit 2
As far as I understand your question, it will not work to have a query context in the config.
Look at i18n middleware: request.locale - > store - > update modules (router, vuetify, moment, etc.)
https://nuxtjs.org/examples/i18n/
~/middleware/gtm.js
export default function ({ app, store }) {
// app.$gtm contains id, you can set another from store
}
don't forget to add middleware to the page
page.vue
export default {
middleware: ['gtm']
}

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

Angular2 routing issues - Zone aware error No elements in sequence

I have searched quite a bit and couldn't find any answer that solved my problem. So I am posting this question.
My issue is very similar to this question. Angular 2.0.1 Router EmptyError: no elements in sequence
But I couldn't resolve it even by adding pathMatch: 'full',.
I am getting an intermittent zonewaware error when try to navigate from a list table (navigates to detail view)
below is my module.
#NgModule({
imports: [
CommonModule,
RouterModule.forChild([
{
path: 'teams',
component: TeamsListComponent,
pathMatch: 'full',
resolve: {
TeamTemplate: TeamListTemplatesResolver
},
canActivate: [AuthenticatedGuard]
}, {
path: 'teams/:id',
component: TeamFormComponent,
pathMatch: 'full',
resolve: {
team: TeamFormTeamResolver,
resources: TeamFormResourcesResolver
},
canActivate: [AuthenticatedGuard]
}
]),
my authGuard service has a canActivate method which just returns a boolean.
public canActivate(): boolean {
if (this.authService.isLoggedIn()) {
return true;
}
this.router.navigate(['/logout', { redirect: location.pathname }]);
return false;
}
And here is the error:
Zone aware error
I could get a router event log with {enableTracing: true}:
Router Event: NavigationStart
Router Event: RoutesRecognized
Router Event: GuardsCheckStart
Router Event: GuardsCheckEnd
Router Event: ResolveStart
Router Event: NavigationError
Thanks for anyone who looked at this issue. I got the answer to my question.
As I described, I have few resolvers while I route to the detail page. On one of those resolvers there's a logic to get elements.
public resolve(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): Observable<T[]> {
return this.service.browse({}).first();
}
https://stackoverflow.com/a/42346203/5162622 - As mentioned here, the first() is sending error notification as there's no values. So I replaced it with take(1) and all looks good.
As I mentioned above in the comment, it was good to know how to do event tracking while routing. That's how I could track this down.

Resources