Common Angular services for communicating with backend, authentication and user managing.
In progress...
Ronas IT Angular Common working with cookies. One of the main advantages of this approach is that cookies can be HTTP-only. It makes them read-protected on the client side, that improves safety against any Cross-site scripting (XSS) attacks. Cookie-based authentication allows using this services in Server-Side Rendering (SSR) applications.
Install Ronas IT Angular Common:
npm i @ronas-it/angular-common --save
- Add
ApiModule
toAppModule
imports:
import { ApiModule } from '@ronas-it/angular-common';
import { configuration } from '@configuration';
@NgModule({
imports: [
ApiModule.forRoot({
apiUrl: configuration.api.url
}),
...
],
...
})
export class AppModule { }
- Inject
ApiService
and use it:
import { ApiService } from '@ronas-it/angular-common';
import { Injectable } from '@angular/core';
@Injectable()
export class ProductService {
constructor(
private apiService: ApiService
) { }
public delete(id: number): Observable<void> {
return this.apiService.delete(`/products/${id}`);
}
...
}
- Add
CookieModule
toAppModule
imports:
import { CookieModule } from '@ronas-it/angular-common';
@NgModule({
imports: [
CookieModule.forRoot({
defaultOptions: { path: '/', /* other cookie options ... */ }
}),
...
],
...
})
export class AppModule { }
- Inject
CookieService
and use it:
import { BehaviorSubject, Subject } from 'rxjs';
import { CookieService } from '@ronas-it/angular-common';
import { Injectable } from '@angular/core';
@Injectable()
export class CookiePopupFacade {
private isCookiesAccepted$: Subject<boolean>;
constructor(
private cookieService: CookieService
) {
this.isCookiesAccepted$ = new BehaviorSubject(this.cookieService.get('isCookiesAccepted') === 'true');
}
public acceptCookies(): void {
this.isCookiesAccepted$.next(true);
this.cookieService.put('isCookiesAccepted', 'true', { maxAge: 4e10 });
}
...
}
- (SSR Only) Add providers for
REQUEST
andRESPONSE
injection tokens from@nguniversal/express-engine/tokens
inserver.ts
:
server.get('*', (req, res) => {
res.render(indexHtml, {
req,
res,
providers: [
{
provide: APP_BASE_HREF,
useValue: req.baseUrl
},
{
provide: REQUEST,
useValue: req
},
{
provide: RESPONSE,
useValue: res
}
]
});
});
- (SSR Only) Set
requestToken
andresponseToken
parameters in theCookieModule
config:
import { CookieModule } from '@ronas-it/angular-common';
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
@NgModule({
imports: [
CookieModule.forRoot({
defaultOptions: { /* ... */ },
requestToken: REQUEST,
responseToken: RESPONSE
}),
...
],
...
})
export class AppModule { }
Note: This module depends on ApiModule
and AuthModule
. Please make sure to
install them prior to installing this module.
- Create a
User
model and extend it fromAbstractUser
:
import { AbstractUser } from '@ronas-it/angular-common';
import { Expose } from 'class-transformer';
export class User extends AbstractUser {
@Expose({ groups: ['main'] })
public id: number;
@Expose({ groups: ['main'] })
public name: string;
@Expose({ groups: ['main'] })
public email: string;
}
- Create a
UserService
and extend it fromCommonUserService
:
import { UserService as CommonUserService } from '@ronas-it/angular-common';
@Injectable()
export class UserService extends CommonUserService<User> {
/* Define custom methods or override existing methods here. */
}
- Create a
UserModule
and addCommonUserModule
to imports:
import { NgModule } from '@angular/core';
import { User } from './models';
import { UserModule as CommonUserModule } from '@ronas-it/angular-common';
import { UserService } from './user.service';
@NgModule({
imports: [
CommonUserModule.forRoot({
userModel: User,
userService: UserService
}),
...
],
...
})
export class UserModule { }
- Inject
UserService
and use it:
import { Action } from '@ngrx/store';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { AuthService } from '@shared/auth';
import { catchError, filter, mergeMap, switchMap, withLatestFrom } from 'rxjs/operators';
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { User } from '../models';
import { userActions } from './actions';
import { UserService } from '../user.service';
@Injectable()
export class UserEffects {
public refreshProfile$: Observable<Action> = createEffect(
() => this.actions$.pipe(
ofType(userActions.refreshProfile),
withLatestFrom(this.authService.isAuthenticated$),
filter(([_, isAuthenticated]) => isAuthenticated),
switchMap(() => {
return this.userService.refreshProfile()
.pipe(
mergeMap((user: User) => [
userActions.updateProfile({ profile: user }),
userActions.refreshProfileSuccess()
]),
catchError((response: HttpErrorResponse) => of(userActions.refreshProfileFailure({ response })))
);
})
)
);
constructor(
private actions$: Actions,
private authService: AuthService,
private userService: UserService
) { }
}
Note: This module depends on ApiModule
, CookieModule
and UserModule
. Please make sure to
install them prior to installing this module.
- Create an
AuthService
and extend it fromCommonAuthService
:
import { AuthService as CommonAuthService } from '@ronas-it/angular-common';
import { Injectable } from '@angular/core';
import { User } from '@shared/user';
@Injectable()
export class AuthService extends CommonAuthService<User> {
/* Define custom methods or override existing methods here. */
}
- Create an
AuthModule
and addCommonAuthModule
to imports:
import { AuthModule as CommonAuthModule } from '@ronas-it/angular-common';
import { AuthService } from './auth.service';
import { configuration } from '@configuration';
import { NgModule } from '@angular/core';
@NgModule({
imports: [
CommonAuthModule.forRoot({
unauthorizedRoutes: configuration.api.unauthorized_routes,
authService: AuthService,
// Optionally, you can pass `unauthenticatedRoute` parameter that
// specifies the route to redirect to after logout or when a user is
// not authenticated to view some page. By default it is set to `/login`.
unauthenticatedRoute: '/'
}),
...
],
...
})
export class AuthModule { }
- Inject
AuthService
and use it:
import { Action } from '@ngrx/store';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { authActions } from './actions';
import { AuthService } from '../auth.service';
import { catchError, exhaustMap, map } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
@Injectable()
export class AuthEffects {
public authorize$: Observable<Action> = createEffect(
() => this.actions$.pipe(
ofType(authActions.authorize),
exhaustMap((action) => {
return this.authService
.authorize(action.credentials)
.pipe(
map((response) => authActions.authSuccess({ response })),
catchError((response) => of(authActions.authFailure({ response })))
);
})
)
);
constructor(
private actions$: Actions,
private authService: AuthService
) { }
}
ApiModule.forRoot(config: ApiConfig)
Name | Type | Required | Description |
---|---|---|---|
apiUrl |
string |
Yes | Endpoint that allows you to access an API |
trailingSlash |
boolean |
No | The need for trailing slash (https://api.your-service.com/login/ for example) |
enableArrayKeys |
boolean |
No | Enabling array keys for http params |
fileKeys |
Array<string> |
No | List of the file keys for http params |
Field | Type |
---|---|
apiUrl |
string |
trailingSlash |
string |
fileKeys |
Array<string> |
Method | Arguments | Return type |
---|---|---|
get<T> |
endpoint: string, params: any, options: object |
Observable<T> |
post<T> |
endpoint: string, data: any, options: object |
Observable<T> |
put<T> |
endpoint: string, data: any, options: object |
Observable<T> |
delete<T> |
endpoint: string, params: any, options: object |
Observable<T> |
CookieModule.forRoot(config: CookieConfig)
Name | Type | Required | Description |
---|---|---|---|
defaultOptions |
CookieOptions |
No | Cookie options that will be used if not specified in the put method |
requestToken |
InjectionToken<Request> |
No | Request injection token from @nguniversal/express-engine/tokens for cookies support in SSR |
responseToken |
InjectionToken<Response> |
No | Response injection token from @nguniversal/express-engine/tokens for cookies support in SSR |
Name | Type |
---|---|
maxAge |
number |
expires |
Date |
path |
string |
domain |
string |
secure |
boolean |
sameSite |
boolean | 'lax' | 'strict' | 'none' |
Field | Type |
---|---|
cookieString |
string |
Method | Arguments | Return type |
---|---|---|
get |
key: TKey |
string | null |
getObject |
key: TKey |
object | null |
getAll |
Record<string, string> |
|
hasKey |
key: TKey |
boolean |
put |
key: TKey, value: string, options?: CookieOptions |
void |
putObject |
key: TKey, value: object, options?: CookieOptions |
void |
remove |
key: TKey, options?: CookieOptions |
void |
removeAll |
options?: CookieOptions |
void |
CommonAuthModule.forRoot(config: AuthConfig)
Name | Type | Required | Description |
---|---|---|---|
unauthorizedRoutes |
Array<string> |
Yes | Routes that don't need authorization (public routes, e.g. login, registration and forgot password pages) |
authService |
new (...args: Array<any>) => any |
Yes | Service that will be used in your app |
unauthenticatedRoute |
string |
No | Route to redirect to after logout or when a user is not authenticated to view some page. By default it is set to /login |
disableRedirectAfterUnauthorize |
boolean |
No | Whether to redirect to unauthenticatedRoute after logout or when a user is not authenticated to view some page. By default it is set to false |
authenticatedRoute |
string |
No | Route to redirect after successful login |
loginEndpoint |
string |
No | Endpoint for login, e.g. /api/token |
refreshTokenEndpoint |
string |
No | Endpoint for refreshing token, e.g. /api/token/refresh |
refreshTokenEndpointMethod |
'get' | 'post' |
No | HTTP Method that will be used for calling endpoint to refresh token |
isAuthenticatedField |
string |
No | Field for cookie |
rememberField |
string |
No | Field for cookie |
cookiesExpirationDays |
number |
No | Expiration for authentication cookies when call authorize with remember flag set to true. By default it is set to 365 |
Static constant | Type |
---|---|
DEFAULT_LOGIN_ENDPOINT |
string |
DEFAULT_UNAUTHENTICATED_ROUTE |
string |
DEFAULT_IS_AUTHENTICATED_FIELD |
string |
DEFAULT_REFRESH_TOKEN_ENDPOINT |
string |
DEFAULT_REMEMBER_FIELD |
string |
DEFAULT_COOKIES_EXPIRATION_DAYS |
number |
Field | Type |
---|---|
isTokenRefreshing$ |
Observable<boolean> |
isAuthenticated$ |
Observable<boolean> |
cookiesExpiresDate |
Date |
Method | Arguments | Return type |
---|---|---|
authorize<T> |
credentials: AuthCredentials & T, remember: boolean |
Observable<AuthResponse<User>> |
manuallyAuthorize |
authResponse: object, remember: boolean = true |
Observable<AuthResponse<User>> |
unauthorize |
void |
|
refreshToken |
Observable<HttpResponse<void>> |
|
setIsAuthenticated |
remember: boolean |
void |
resetIsAuthenticated |
void |
|
resetRemember |
void |
Field | Type | Required |
---|---|---|
email |
string |
No |
password |
string |
Yes |
Field | Type | Required |
---|---|---|
user |
User |
No |
UserModule.forRoot(config: UserConfig)
Name | Type | Required | Description |
---|---|---|---|
userModel |
new (user: any) => any |
Yes | Model (class) for user |
userService |
new (...args: Array<any>) => any |
Yes | Your UserService implementation |
profileRelations |
Array<string> |
No | Relations for getting profile request. For example: /profile?with[]=company&with[]=clients |
profileRelationsKey |
string |
No | with by default |
Field | Type |
---|---|
profile$ |
Observable<User> |
Method | Arguments | Return type |
---|---|---|
refreshProfile |
Observable<User> |
|
loadProfile |
Observable<User> |
|
updateProfile |
user: User |
Observable<void> |
updatePassword |
userPasswords: UserPasswords |
Observable<void> |
setProfile |
user: User |
void |
patchProfile |
user: Partial<User> |
void |
resetRemember |
void |
|
resetProfile |
void |
|
userToPlain |
user: User, options?: ClassTransformOptions |
Object |
plainToUser |
plain: object, options?: ClassTransformOptions |
User |
Field | Type | Required |
---|---|---|
id |
number | string |
Yes |
Contributions to Ronas IT Angular Common are welcome. The contribution guide can be found in the Contributing guide.
Ronas IT Angular Common is open-sourced software licensed under the MIT license.