I have an angular application in which I'm trying to integrate with Auth0. I followed these two tutorials:
https://auth0.com/docs/quickstart/spa/angular2/01-login
https://auth0.com/docs/quickstart/spa/angular2/02-calling-an-api
Here is the setup of my project:
AuthService
Copy+Pasted from the first tutorial link
LoginController
export class LoginComponent implements OnInit {
constructor(private authService: AuthService) { }
ngOnInit(): void {
this.authService.login();
}
}
App-Routing Module
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'profile', component: ProfileComponent, resolve: { queues: ProfileResolver}, canActivate: [AuthGuard]}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: InterceptorService,
multi: true
}
]
})
export class AppRoutingModule { }
I'm getting logged in successfully, however when auth0 does its callback, it navigates to http://localhost:4200/profile?code=[code]&state=[state], which then Angular throws "Cannot match any routes".
My questions are:
What are the code and state parameters used for?
What should I do to properly handle these so my site can route to /profile properly?
Thanks a bunch!
This is a nasty gotcha with auth0-spa-js that caused a big headache for me in production. The issue is describe in this thread. How I solved it was changing the last line of the handleAuthCallback function in the auth service from:
this.router.navigate([targetRoute])
to:
this.router.navigate([name-of-path-you're-redirecting-to]).
Related
I can't believe I can't find this situation already covered here in SO:
(I found examples with additional parameter with and without for each single route, but it's unacceptable)
So I have
RouterModule.forRoot([
{
path: 'home',
component: HomeComponent
},
{
path: 'news',
component: NewsComponent
},
{
path: 'newsDetail/:id',
component: NewsDetailComponent
},
...
})
So the example URLs would be
http://somewhere.com/home
http://somewhere.com/news
http://somewhere.com/newsDetail/10
What if I want to add optional parameter to each of those URLs, so I can explicitly call another localization directly in URL (for permalinks):
http://somewhere.com/home/en
http://somewhere.com/news/en
http://somewhere.com/newsDetail/10/en
So it should work with and without "/en" at the end - and of course adding to each and every route (same route with optional /:language) is not the answer (imagine dozens of pages involved, many of them already with their own parameters)
If you want the language parameter to be the first, you can do the following. You will first have to declare an empty app or whatever root component and use this in the bootstrap instead of the AppComponent:
#Component({
selector: 'app-root',
template: `<router-outlet></router-outlet>`
})
export class RootComponent {}
Then create a module from your current routes, if you do not have that already. Call it AppRoutingModule.
export const AppRoutes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: 'news', component: NewsComponent },
{ path: 'newsDetail/:id', component: NewsDetailComponent }
];
#NgModule({
imports: [RouterModule.forFeature(AppRoutes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
Then create a RootRoutingModule, which will do the magic:
export const RootRoutes: Routes = [
{ path: '', loadChildren: () => import('./app.module').then((m) => m.AppModule) },
{ path: 'en', loadChildren: () => import('./app.module').then((m) => m.AppModule) }
];
#NgModule({
imports: [RouterModule.forRoot(AppRoutes)],
exports: [RouterModule],
})
export class RootRoutingModule {}
The issue with this, is that you'll have to hardcode all the languages you might support, as I don't think a :language parameter will work
So basically, create a root module which will do the language routing and the bootstrapping
I'm trying to set my LOCALE_ID token before my angular app bootstraps by using the documentation method:
import { LOCALE_ID } from '#angular/core';
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule, {
providers: [{provide: LOCALE_ID, useValue: 'fr-FR' }]
});
When checking the token in my AppModule's constructor, it seems like it's been reset to defaults
export class AppModule {
constructor(#Inject(LOCALE_ID) private locale: string) {
console.log('Locale - App module', this.locale);
}
}
Outputs: Locale - App module en-US
What am I missing here ?
Here is a Stackblitz reproducing the issue:
https://stackblitz.com/edit/angular-2rdtb6
You should be passing providers to platform injector not to compiler injector:
platformBrowserDynamic([ {provide: LOCALE_ID, useValue: 'fr-FR' }])
\/
extraProviders
.bootstrapModule(AppModule);
Beware that to test it in stackblitz you have to reload application since Angular creates platform only once.
See also:
What you always wanted to know about Angular Dependency Injection tree
You can try just set the provider in app.module.ts
#NgModule({
providers: [
{
// your objects here
}
]
})
My Anuglar 4 app has multiple routes, in below example there are only two of them - Logging and List of items. So basically there are two routes: http://localhost:4200/#/ and http://localhost:4200/#/items. When I am at the http://localhost:4200/#/items and reload the page it automatically navigates me to http://localhost:4200/#/ which from my point of view is wrong behaviour. Is there a nice way how to prevent it? And when currently I am at the http://localhost:4200/#/items and reload the page to stay at the same page?
Below I post kind of configuration which might help you:
<base href="/">
imports: [
RouterModule.forRoot([
{path: '', component: LoginComponent},
{path: 'items', component: ItemListComponent, canActivate: [AuthGuard]},
])
],
providers: [
{provide: HTTP_INTERCEPTORS, useClass: CustomHttpInterceptor, multi: true},
{provide: LocationStrategy, useClass: HashLocationStrategy},
AuthenticationService,
AuthGuard,
],
AuthGuard:
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthenticationService) {
}
canActivate(next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this.authService.isLoggedIn;
}
}
#Injectable()
export class AuthenticationService extends DataService{
private _isLoggedIn: boolean;
constructor(http: HttpClient) {
super(http);
this._isLoggedIn = false;
this.url = '/api/auth';
}
get isLoggedIn(): boolean{
return this._isLoggedIn;
}
set isLoggedIn(value: boolean){
this._isLoggedIn = value;
}
And the server returns the token. For your information after reloading there is still valid token keeping in localStorage
This has nothing to do with authtoken, but all about your canActivate. When your app is initialized, means that app is destroyed and recreated. So when on a route that requires the logged in status, you have initially declared _isloggedIn as false. So the guard is working properly and redirecting you to your LoginComponent.
You would want to use localStorage or something else to persist your logged in status in case of page refresh.
add pathMatch: 'full'
{path: '', component: LoginComponent, pathMatch: 'full'},
Angular is a Single Page Application based framework. Page refresh will be done only when the app is initialized. You can make use of two-way binding, make a call to back-end and update the list of items received in response.
You can also try router.navigate(['/items']), it will work only when you are navigating from other components.
I am trying to build the angular version 5 application for production with this command of angular cli version 1.5.2:
ng build --prod
but it gives me this error :
ERROR in Error: Error encountered resolving symbol values statically. Calling fu nction 'FlashMessagesModule', function calls are not supported. Consider replaci ng the function or lambda with a reference to an exported function, resolving sy mbol AppModule in D:/Project/mean-auth-app/angular-src/src/app/app.module.ts, re solving symbol AppModule in D:/Project/mean-auth-app/angular-src/src/app/app.mod ule.ts
It seems angular v5.0 has a conflict with angular2-flash-messages module version 2.0.0.
I did exactly the same thing here to install and setup the flash messages module. I searched but i couldn't find any useful hint. Some people call it a bug and some people could solve their problem with uninstall/install the problematic package.
My app module :
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { RouterModule, Routes } from '#angular/router';
import { HttpModule } from '#angular/http';
import { AppComponent } from './app.component';
import { NavbarComponent } from './components/navbar/navbar.component';
import { LoginComponent } from './components/login/login.component';
import { RegisterComponent } from './components/register/register.component';
import { HomeComponent } from './components/home/home.component';
import { DashboardComponent } from './components/dashboard/dashboard.component';
import { ProfileComponent } from './components/profile/profile.component';
import { AccountService } from './services/account.service';
import { FlashMessagesModule } from 'angular2-flash-messages';
import { JwtModule } from '#auth0/angular-jwt';
import { HttpClientModule } from '#angular/common/http';
import { AuthGuard } from './services/auth-guard.service';
const routes = [
{ path: '', component: HomeComponent },
{ path: 'register', component: RegisterComponent },
{ path: 'login', component: LoginComponent },
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard]},
{ path: 'profile', component: ProfileComponent, canActivate: [AuthGuard]}
];
#NgModule({
declarations: [
AppComponent,
NavbarComponent,
LoginComponent,
RegisterComponent,
HomeComponent,
DashboardComponent,
ProfileComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
FlashMessagesModule.forRoot(),
RouterModule.forRoot(routes),
HttpClientModule,
JwtModule.forRoot({
config: {
tokenGetter: () => {
return localStorage.getItem('token');
},
whitelistedDomains: ['localhost:4200']
}
})
],
providers: [AccountService, AuthGuard],
bootstrap: [AppComponent]
})
export class AppModule { }
I appreciate any hint to solve this problem.
EDIT: The following will also break the --prod build, but the message marks another problem.
I took a look at the angular2-flash-messages package and the error lies in their code:
They don't add the required metadata of the angular compiler.
There is no solution until following issue is fixed: https://github.com/moff/angular2-flash-messages/issues/31
Old answer:
You aren't allowed to use lambda functions in a decorator.
The error lies here:
config: {
tokenGetter: () => { // <- here
return localStorage.getItem('token');
},
whitelistedDomains: ['localhost:4200']
}
The problem is, that angular will not be able to store all required informations about the decorator if you use a lambda function, but an exported function can be used.
You have to rewrite it to:
export function tokenGetter() {
return localStorage.getItem('token');
}
And you should be able to use it like this in your code:
config: {
tokenGetter: tokenGetter,
whitelistedDomains: ['localhost:4200']
}
Specifying paths to #angular inside AngularCLI's tsconfig.json prevented the error from happening.
"paths": { "#angular/*": ["../node_modules/#angular/*"] }
Reference : link
I am writing some unit tests for my component and i am getting this cryptic error message. I found a similar question at Angular 2 unit testing - getting error Failed to load 'ng:///DynamicTestModule/module.ngfactory.js' but the answers did not help me solve my issue. I am angular 4.3.2
Here's the component i am writing the test for:
import {Component} from '#angular/core';
import {Router} from '#angular/router';
import {NotificationService} from '../common/notification/notification.service';
import {SessionService} from '../common/session/session.service';
import {Login} from './login.model';
#Component({
selector: 'cc-login-form',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'],
})
export class LoginComponent {
model: Login = new Login('', '');
constructor(private sessionService: SessionService,
private router: Router,
private notificationService: NotificationService) {
}
onSubmit() {
this.sessionService
.login(this.model.email, this.model.password)
.subscribe(
sessionInfo => {
this.notificationService.showSuccess('notification.successfully.logged.in');
this.router.navigate([`/cc/list`]);
},
error => this.notificationService.showError('notification.invalid.login')
);
}
}
And here is the test file:
import {async, ComponentFixture, TestBed} from '#angular/core/testing';
import {FormsModule} from '#angular/forms';
import {Router} from '#angular/router';
import {TranslateModule, TranslateService} from '#ngx-translate/core';
import {NotificationService} from '../common/notification/notification.service';
import {NotificationServiceStub} from '../common/notification/tests/NotificationServiceStub';
import {SessionService} from '../common/session/session.service';
import {SessionServiceStub} from '../common/session/tests/SessionServiceStub';
import {RouterStub} from '../common/tests/RouterStub';
import {TranslateServiceStub} from '../common/translate/tests/TranslateServiceStub';
import {LoginComponent} from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
TranslateModule
],
declarations: [LoginComponent],
providers: [
{provide: SessionService, useClass: SessionServiceStub},
{provide: Router, useClass: RouterStub},
{provide: NotificationService, useClass: NotificationServiceStub},
{provide: TranslateService, useClass: TranslateServiceStub},
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});
When running the test i get the following on chrome console:
zone.js:2642 XMLHttpRequest cannot load ng:///DynamicTestModule/LoginComponent.ngfactory.js. Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
(anonymous) # zone.js:2642
zone.js:195 Uncaught DOMException: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'ng:///DynamicTestModule/LoginComponent.ngfactory.js'.
at http://localhost:9876/_karma_webpack_/webpack:/Users/pedrompg/Documents/quandoo/fe/chains-center/~/zone.js/dist/zone.js:2642:1
at XMLHttpRequest.proto.(anonymous function) [as send] (
Any can help me with that?
EDIT - 1
Here's the services/stubs implementation
SessionServiceStub
export class SessionServiceStub implements ISessionService {
login(login: string, password: string): Observable<any> {
return Observable.of({merchantId: 123});
}
logout(): Observable<any> {
throw new Error('Method not implemented.');
}
validateSessionToken(): Observable<any> {
throw new Error('Method not implemented.');
}
}
SessionService
#Injectable()
export class SessionService implements ISessionService {
constructor(private http: CcHttpClient, private router: Router, private localSessionService: LocalSessionService) {
}
login(login: string, password: string): Observable<any> {
return this.http.post(`api/sessions`, {login: login, password: password}).map((res: Object) => {
this.localSessionService.createSession(res);
return res;
});
}
}
RouterStub
export class RouterStub {
navigate(commands: any[], extras?: NavigationExtras): Promise<boolean> {
return Promise.resolve(true);
};
}
TranslationServiceStub
export class TranslateServiceStub {
instant(key: string | Array<string>, interpolateParams?: Object): string | any {
return 'translation';
};
}
NotificationServiceStub
export class NotificationServiceStub implements INotificationService {
showToast(type, text, title, defaultTitle): Promise<Toast> {
return Promise.resolve(null);
}
showSuccess(msg, title?): Promise<Toast> {
return Promise.resolve(null);
}
showError(msg, title?): Promise<Toast> {
return Promise.resolve(null);
}
}
EDIT 2
Changing my TestBed config to the following removed the error but brought a new one:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
HttpClientModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpTranslateLoaderFactory,
deps: [HttpClient]
}
})
],
declarations: [LoginComponent],
providers: [
{provide: SessionService, useClass: SessionServiceStub},
{provide: Router, useClass: RouterStub},
{provide: NotificationService, useClass: NotificationServiceStub},
]
})
.compileComponents();
}));
Now the error message is
TypeError: Cannot read property 'assertPresent' of undefined
at resetFakeAsyncZone home/pedrompg/Documents/quandoo/fe/chains-center/~/#angular/core/#angular/core/testing.es5.js:304:1)
at Object.<anonymous> home/pedrompg/Documents/quandoo/fe/chains-center/~/#angular/core/#angular/core/testing.es5.js:1001:1)
at ZoneQueueRunner.webpackJsonp.../../../../zone.js/dist/jasmine-patch.js.jasmine.QueueRunner.ZoneQueueRunner.execute home/pedrompg/Documents/quandoo/fe/chains-center/~/zone.js/dist/jasmine-patch.js:132:1)
Which happens at this function:
function resetFakeAsyncZone() {
_fakeAsyncTestZoneSpec = null;
ProxyZoneSpec.assertPresent().resetDelegate(); //ProxyZoneSpec is undefined here for whatever reason
}
This is a problem with the Angular Cli version 1.2.2 or newer. Run your test with --sourcemaps=false and you will get the right error messages.
In Angular 4-5
ng test --sourcemaps=false
or
ng test -sm=false
In Angular 6+
ng test --source-map=false
See details here: https://github.com/angular/angular-cli/issues/7296
I just ran into this error and the problem was my mocks.
In the component.ngOnInit i used this.route.paramMap.subscribe(...)
where route is an ActivatedRoute instance
In my test i provided a mock service like this :
providers: [
{ provide: ActivatedRoute, useValue: { snapshot: { params: { id: 1 } } } }
]
And in fact i missed to mock the paramMap method
Then i fix it adding a paramMap properties like this
providers: [
{ provide: ActivatedRoute, useValue: { snapshot: { params: { id: 1 } }, paramMap: Observable.of({get: () => 1}) } }
]
Then i don't have anymore this stupid error.
So for you, i expect the class SessionServiceStub to be incomplete or erroneous. Does it get a login method that return an Observable ?
If it's not the problem you can check the NotificationServiceStub
You should use a debugger (with Webstorm it's easy to debug step-by-step) to help you.
I encountered the same issue using angular-cli 6, so to get the right error message one should use the following:
ng test --source-map=false
Maybe it will help someone :) .
Been chasing this for hours. Finally discovered that the problem was simply that I had imported HttpClientModule, but not HttpClient:
import { HttpClient, HttpClientModule } from '#angular/common/http';
All those CORS errors, and '[Script Loader]', DOMException{stack: 'Error: Failed to execute 'send' on 'XMLHttpRequest' stuff, and it came down to just not having HttpClient!