I'm trying to implement oauth process through google.
I have multiple scenarios of using it, so I have multiple endpoints where google-oauth auth process should be inited.
It looks like:
#Controller('auth/google')
export class GoogleAuthController {
#Get('register')
async register(
#Res() res: Response,
) {
res.redirect("oautgoogleurl.com?state=register")
}
#Get('login')
async login(
#Res() res: Response,
) {
res.redirect("oautgoogleurl.com?state=login")
}
#Get('link')
async link(
#Res() res: Response,
) {
res.redirect("oautgoogleurl.com?state=link")
}
}
And in every case I have almost the same url for redirection except the "state" parameter. I want to move this logic of forming url to some other place, where it should be?
Can it be private method inside controller (without get/post/etc decorators)?
#Controller('auth/google')
export class GoogleAuthController {
// ... all above code ...
private getRedirectUrl(state: string) {
return 'oautgoogleurl.com?state=' + state;
}
}
Or do I have to move into another place?
I also have GoogleAuthService with the same methods names as controller (register/login/link). Should I place "getRedirectUrl" here?
Or maybe create some additional GoogleAuthHelper class and place this logic here?
I am trying to update (add, remove) queryParams from a component. In angularJS, it used to be possible thanks to :
$location.search('f', 'filters[]'); // setter
$location.search()['filters[]']; // getter
I have an app with a list that the user can filter, order, etc and I would like to set in the queryParams of the url all the filters activated so he can copy/paste the url or share it with someone else.
However, I don't want my page to be reloaded each time a filter is selected.
Is this doable with the new router?
You can navigate to the current route with new query params, which will not reload your page, but will update query params.
Something like (in the component):
import {ActivatedRoute, Router} from '#angular/router';
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
) { }
public myMethodChangingQueryParams() {
const queryParams: Params = { myParam: 'myNewValue' };
this.router.navigate(
[],
{
relativeTo: activatedRoute,
queryParams: queryParams,
queryParamsHandling: 'merge', // remove to replace all query params by provided
});
}
Note, that whereas it won't reload the page, it will push a new entry to the browser's history. If you want to replace it in the history instead of adding new value there, you could use { queryParams: queryParams, replaceUrl: true }.
EDIT:
As already pointed out in the comments, [] and the relativeTo property was missing in my original example, so it could have changed the route as well, not just query params. The proper this.router.navigate usage will be in this case:
this.router.navigate(
[],
{
relativeTo: this.activatedRoute,
queryParams: { myParam: 'myNewValue' },
queryParamsHandling: 'merge'
});
Setting the new parameter value to null will remove the param from the URL.
#Radosław Roszkowiak's answer is almost right except that relativeTo: this.route is required as below:
constructor(
private router: Router,
private route: ActivatedRoute,
) {}
changeQuery() {
this.router.navigate(['.'], { relativeTo: this.route, queryParams: { ... }});
}
In Angular 5 you can easily obtain and modify a copy of the urlTree by parsing the current url. This will include query params and fragments.
let urlTree = this.router.parseUrl(this.router.url);
urlTree.queryParams['newParamKey'] = 'newValue';
this.router.navigateByUrl(urlTree);
The "correct way" to modify a query parameter is probably with the
createUrlTree like below which creates a new UrlTree from the current while letting us modify it using NavigationExtras.
import { Router } from '#angular/router';
constructor(private router: Router) { }
appendAQueryParam() {
const urlTree = this.router.createUrlTree([], {
queryParams: { newParamKey: 'newValue' },
queryParamsHandling: "merge",
preserveFragment: true });
this.router.navigateByUrl(urlTree);
}
In order to remove a query parameter this way you can set it to undefined or null.
The answer with most vote partially worked for me. The browser url stayed the same but my routerLinkActive was not longer working after navigation.
My solution was to use lotation.go:
import { Component } from "#angular/core";
import { Location } from "#angular/common";
import { HttpParams } from "#angular/common/http";
export class whateverComponent {
constructor(private readonly location: Location, private readonly router: Router) {}
addQueryString() {
const params = new HttpParams();
params.append("param1", "value1");
params.append("param2", "value2");
this.location.go(this.router.url.split("?")[0], params.toString());
}
}
I used HttpParams to build the query string since I was already using it to send information with httpClient. but you can just build it yourself.
and the this._router.url.split("?")[0], is to remove all previous query string from current url.
Try
this.router.navigate([], {
queryParams: {
query: value
}
});
will work for same route navigation other than single quotes.
If you want to change query params without change the route. see below
example might help you:
current route is : /search
& Target route is(without reload page) : /search?query=love
submit(value: string) {
this.router.navigate( ['.'], { queryParams: { query: value } })
.then(_ => this.search(q));
}
search(keyword:any) {
//do some activity using }
please note : you can use this.router.navigate( ['search'] instead of this.router.navigate( ['.']
I ended up combining urlTree with location.go
const urlTree = this.router.createUrlTree([], {
relativeTo: this.route,
queryParams: {
newParam: myNewParam,
},
queryParamsHandling: 'merge',
});
this.location.go(urlTree.toString());
Not sure if toString can cause problems, but unfortunately location.go, seems to be string based.
Better yet - just HTML:
<a [routerLink]="[]" [queryParams]="{key: 'value'}">Your Query Params Link</a>
Note the empty array instead of just doing routerLink="" or [routerLink]="''"
First, we need to import the router module from angular router and declare its alias name
import { Router } from '#angular/router'; ---> import
class AbcComponent implements OnInit(){
constructor(
private router: Router ---> decalre alias name
) { }
}
1. You can change query params by using "router.navigate" function and pass the query parameters
this.router.navigate([], { queryParams: {_id: "abc", day: "1", name: "dfd"}
});
It will update query params in the current i.e activated route
The below will redirect to abc page with _id, day
and name as query params
this.router.navigate(['/abc'], { queryParams: {_id: "abc", day: "1", name:
"dfd"}
});
It will update query params in the "abc" route along with three query paramters
For fetching query params:-
import { ActivatedRoute } from '#angular/router'; //import activated routed
export class ABC implements OnInit {
constructor(
private route: ActivatedRoute //declare its alias name
) {}
ngOnInit(){
console.log(this.route.snapshot.queryParamMap.get('_id')); //this will fetch the query params
}
Angular's Location service should be used when interacting with the browser's URL and not for routing. Thats why we want to use Location service.
Angulars HttpParams are used to create query params. Remember HttpParams are immutable, meaning it has to be chained when creating the values.
At last, using this._location.replaceState to change to URL without reloading the page/route and native js location.path to get the url without params to reset the params every time.
constructor(
private _location: Location,
) {}
...
updateURLWithNewParamsWithoutReloading() {
const params = new HttpParams().appendAll({
price: 100,
product: 'bag'
});
this._location.replaceState(
location.pathname,
params.toString()
);
}
I've had an interesting situation where we used only one component for all routes we had. This is what routes looked like:
const routes: Routes = [
{
path: '',
component: HomeComponent,
children: [
{ path: 'companies', component: HomeComponent },
{ path: 'pipeline', component: HomeComponent },
// ...
]
},
// ...
];
So, basically, paths /, /companies and /pipeline were all having the same component that had to be loaded. And, since Angular prevents reloading of the components if they were previously loaded in the DOM, Router's navigate method returned a Promise that always resolved with null.
To avoid this, I had to use onSameUrlNavigation. By setting this value to 'reload', I managed to make the router navigate to the same URL with the updated query string parameters:
#NgModule({
imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload' })],
exports: [RouterModule]
})
Also you can add BehaviorSubject like:
refresher$ = new BehaviorSubject(null);
I changed my code from that:
this.route.queryParamMap.subscribe(some code)
to:
combineLatest([
this.route.queryParamMap,
this.refresher$
])
.pipe(
map((data) => data[0])
)
.subscribe(here is your the same code)
And when you need refresh your subscription, you need call this:
this.refresher$.next(null);
Also don't forget add unsubscribe from that to ngOnDestroy
I'm building a custom API, with an endpoint defined as follow:
I have a created a Profiling Controller to handle the logic for this endpoint. My controller directory contains 2 files:
controller.ts
import { Request, Response } from 'express';
import ProfilingService from '../../services/profiling.service';
export class Controller {
enrich_profile(req: Request, res: Response): void {
console.log(req);
ProfilingService.enrich_profile(req).then((r) =>
res
.status(201)
.location(`/api/v1/profile_data/enrich_profile/data${r}`)
.json(r)
);
}
}
export default new Controller();
routes.ts
/* eslint-disable prettier/prettier */
import express from 'express';
import controller from './controller';
export default express.
Router()
.post('/enrich_profile', controller.enrich_profile)
;
However, when I sent a call to the endpoint, I get the following error:
And finally, to allow a full picture, here's the content of profiling.service.ts:
import L from '../../common/logger';
interface Profiling {
data: never;
}
export class ProfilingService {
enrich_profile(data: never): Promise<Profiling> {
console.log(data);
L.info(`update user profile using \'${data}\'`);
const profile_data: Profiling = {
data,
};
return Promise.resolve(profile_data);
}
}
export default new ProfilingService();
The error clearly states that POST to the defined endpoint is not possible, and I'm not sure I understand why.
How can I fix this issue?
I'm not sure what was causing the issue, but after cleaning my npm cache, and deleting node_modules about 100X, the issue finally went away.
I am trying to implement sentry error handling into my application, now I have it set up and working as expected.. but now I want to be able to pass user information on the Sentry object for better error logging.
So I have the following setup
export class SentryErrorHandler implements ErrorHandler {
userInfo: UserInfo;
constructor(
private _store: Store<AppState>
) {
this.getUserInfo();
}
getUserInfo() {
this._store.select('userInfo')
.subscribe(result => {
this.userInfo = result;
});
}
handleError(err: any): void {
Sentry.configureScope((scope) => {
scope.setUser({
email: this.userInfo?.emailAddress,
id: this.userInfo?.id?,
});
});
const eventId = Sentry.captureException(err.originalError || err);
Sentry.showReportDialog({ eventId });
}
}
and I am providing the error handler like so in my root module
// ...
{ provide: ErrorHandler, useClass: SentryErrorHandler }
// ...
but what happens is, when I start my application I get the following error
Obviously im doing something wrong here, any help would be appreciated!
This error is happening because without the #Injectable decorator Angular cannot wire up dependencies for the class (even using it in providers).
So all you have to do is add the #Injectable() decorator in your error class.
See a demo here:
https://stackblitz.com/edit/angular-ctutia
I'm currently working on a register page and therefore, I need to post my data to the server. The client-side validation and server-validation works. I know I can handle the client-side errors like *ngIf="(emailAddress.errors?.required || emailAddress.errors?.email) && emailAddress.touched". But how to handle the server-side errors?
In my service, if an error occurs, I simply give it into the component by return throwError(error);
But how can I know to display the specific error in my component if for example there is already someone with this email address? And how do distinguish between email/password validation errors server side?
Depends on the complexity and modules you have.
If you use any kind of state management library such as ngrx or ngxs, I suggest you do as follow:
Define State with a property 'error' which keeps track of the latest server-error.
Api call executed via actions and error is caught and stored to state also via actions. (Do whatever error mapping before saving the error to your State)
Use selectors for component to receive error stream from State.
If you don't have any state management library, you can create a BehaviorSubject within singleton service, and use it to publish server-error as soon as you got into any catchError context.
This way you can write your own http interceptor and have your response inside your component. Also in your component you know your http response is success or error.
You can implement other Http calls(GET, PUT, etc) and also handle your general errors in handleError() function.
app.module.ts
export class AppModule {
constructor() {}
imports: [
HttpClientModule,
],
providers: [
{
provide: Http,
useFactory: httpFactory,
deps: [HttpHandler]
},
],
}
export function httpFactory(httpHandler: HttpHandler) {
return new Http(httpHandler);
}
http.service.ts
export class Http extends HttpClient {
constructor(handler: HttpHandler) {
super(handler);
}
postCall(url: string, body, options?): Observable<any> {
return super.post(url, body, options)
.pipe(
tap(response => { console.log(response) }),
catchError(this.handleError(error)));
}
private handleError<T>(result?: T) {
return (error: any): Observable<T> => {
console.log(error);
return of(result as T);
};
}
}
your.component.ts
export class YourComponent {
constructor(private http: Http) {}
this.http.postCall('URL', {}).subscribe(response => {
if (response instanceof HttpErrorResponse) {
// Your Response is error
} else {
// Your Response is your desired result
}
}