Full sample application built with Angular 4 that follows all steps of Angular Style Guide. Include:
git clone https://github.com/yduartep/angular-full-sample.git
cd angular-full-sample
npm i
Runs the TypeScript compiler and launches the app
npm start
Runs the Angular AoT compiler, rollup, uglify for an optimized bundle, then launches the app
ng build --aot
npm install -g @angular/cli
ng g module heroes
ng g module heroes/heroes-routing
export const heroesRoutes: Routes = <Routes>[{
path: '',
component: HeroesComponent,
children: [
{ path: '', component: HeroListComponent },
{ path: 'detail/:id', component: HeroDetailComponent },
{ path: 'create', component: HeroCreateComponent }
]
}];
@NgModule({
imports: [RouterModule.forChild(heroesRoutes)],
exports: [RouterModule]
})
export class HeroesRoutingModule {}
export const heroesRoutedComponents = [ MyComponent, ...];
.@NgModule({
imports: [HeroesRoutingModule, ...],
declarations: [heroesRoutedComponents]
})
export class HeroesModule { }
[{ "title": "Heroes", "action": "/heroes", "icon": "fa-flash"}, ... ]
const routes: Routes = [..., {path: 'heroes', loadChildren: 'app/heroes/heroes.module#HeroesModule', canActivate: [AuthGuard]},...];
More Info: https://angular-2-training-book.rangle.io/handout/modules/lazy-loading-module.htmlenvironment.qa.ts
export const environment = {
appName: 'My Demo Application',
production: false,
envName: 'qa',
buildVersion: '1.0.0-SNAPSHOT',
buildTimestamp: new Date().toISOString(),
defaultLanguage: 'en',
apiConfig: {
apiEnv: 'dev',
timeExpired: 1200,
credentials: {
clientId: 'userClientId',
clientSecret: 'pwdClient'
},
apiUrls: [
{id: 'HEROES_SERVICE_URL', url: 'http://127.0.0.1:3000/api/heroes', requireAuthBefore: true},
...
{id: 'OAUTH_SERVICE_URL', url: 'http://127.0.0.1:3000/api/oauth/token', requireAuthBefore: false}
],
authService: AuthTypes.OAUTH,
authScheme: AuthScheme.BEARER,
errorHandler: ErrorHandlerTypes.SIMPLE,
loggerService: LoggerTypes.CONSOLE,
}
};
Note: If you want to use the OAuth Client during authentication, the ID of the url should be OAUTH_SERVICE_URL. Also, if your services use the basic authentication, define the property credentials like in the example below.There are other authentication scheme that can be used like BASIC, DIGEST, HOBA or AWS depending of the authentication system you need to integrate.{
"apps": [
"environments": {
...,
"qa": "environments/environment.qa.ts"
}
]
}
ng serve --environment=qa
More Info: http://tattoocoder.com/angular-cli-using-the-environment-option/
The application use the module ngx-translate for translation. The configuration of the TranslateModule is defined in the app.module.ts and exported from the SharedModule, so always be sure the module who expose the component to be translated import the SharedModule. The json files used during translation are stored in the folder /assets/i18n/ with the code of the specific language of translation (es. es.json, en.json, it.json ...).
// app.translate.factory.ts - custom TranslateLoader while using AoT compilation
export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
// app.module.ts
@NgModule({
imports: [...,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: createTranslateLoader,
deps: [HttpClient]
}
})
], ...
})
export class AppModule { }
// shared/shared.module
@NgModule({
imports: [..., TranslateModule, ...],
exports: [..., TranslateModule, ...]
})
export class SharedModule { }
// heroes/heroes.module
@NgModule({ imports: [SharedModule, ...] })
export class HeroesModule { }
When the app.component is instantiated set the following translation settings:
translate.setDefaultLang(defaultLanguage);
.translate.use(localStorage['language'] || defaultLanguage);
Now to translate our words in the HTML view:
Create a pipe that we can use to translate our words: <h2>{{ 'heroesList' | translate }}</h2>
Add the translation ID used in our [language].json files:
// assets/i18n/en.json
{ ..., "heroesList": "List of Super heroes", ...}
// assets/i18n/es.json
{ ..., "heroesList": "Lista de Super heroes", ...}
Each time you change the language, that title will change.
In the core module, there is a language-selector component that could be displayed anywhere to select the language for translation. On this sample application, was added in the header component.
The language-selector component could be initialized in two ways:
<app-language-selector [languages]="languages"></app-language-selector>
<app-language-selector></app-language-selector>
In the second case, the languages will loaded from the file 'assets/data/languages.json'. The format of a language is:
{
"id": "en",
"title": "English",
"icon": "en-EN.png"
}
The icon should be stored in the 'assets/images/flags' folder to be displayed correctly in the component.
assets/i18n/fr.json
{
"id": "fr",
"title": "French",
"icon": "fr-FR.png"
}
More Info: https://github.com/ngx-translate/core/blob/master/README.md
Go to the prototype folder out of the app and define the list of elements for the new api request (/heroes) in the file 'prototype/apiMocks.js'. You can also use the faker.js module to generate massive amounts of fake data:
module.exports = function () {
var faker = require("faker");
return {
heroes: [...]
}
}
If the path of the request is different as the default (default: http://localhost:3000/token, expected: http://localhost:3000/oauth/token), redefine the route in the file routes.json:
{ ..., "/oauth/token": "/token", ... }
Start the mock server using the command: npm run server:mocks
Start the application using the 'mock' environment: npm run client:mocks
More Info:
The application has already installed the library 'font-awesome' and 'bootstrap', so you can create new responsive components with a pack of pre-defined icons. The application also incorporate the library 'ngx-bootstrap' that contains many modules like accordion, alerts, datepicker, progressbar, etc, that could be imported separately in the case you need it. See how to use it from http://valor-software.com/ngx-bootstrap/#/.
The application contains a 'nav' component in the core module that could be initialized in two ways:
<app-nav [items]="menuData"></app-nav>
<app-nav></app-nav>
In the second case, the menus will loaded from the file 'assets/data/menu.json'. The format of a menu is:
{
"title": "Heroes",
"action": "/heroes",
"icon": "fa-flash"
}
The icon is a font-awsone icon. See some example from http://fontawesome.io/examples/.
From the Angular 4.3 version the new HttpClientModule has been introduced as a complete re-implementation of the former HttpModule. The new HttpClient service is included to initiate HTTP request and process responses within your application and the HttpInterceptor to intercept an outgoing HttpRequest
.
So, in the project I have include two new http interceptors AuthInterceptor and TokenInterceptor to check authentication, add authorization token into the header of the request and display / hide spinner before and after complete each request.
The classes are defined as providers of the shared module, in that way, can be imported for each new module automatically.
@NgModule({
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
multi: true
}, {
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
]
})
export class SharedModule {}
More Info:
The application include a login component that could be integrated with any authentication service that implements the interface AuthService present in the core module. The application already includes:
Create new class that implement the interface AuthService. The BaseAuthService is an abstract class that contains some util functionalities like adding or removing user and token info from and to the cookie.
@Injectable()
export class LDAPService extends BaseAuthService implements AuthService {...}
Go to the authFactory function defined in the core module in '/core/factories/auth.factory.ts' and replace the instance created using the new class:
export function authFactory(http: Http): AuthService {
return new LDAPService(...);
}
Restart the application
The application have implemented multiple guards (auth, login, module-import) to protect routes and module loading. All the guards are registered using providers in the 'Core' module.
More Info:
There is a new module 'ui-elements' that contains a list of basic ui components (like input text, password text, number picker, datepicker, select...) with validation support. This 'ui-elements' module is imported and exported in the SharedModule with the objective of use it from all parts of the application. The project provide a list of predefined directives that are used to validates the ui elements (dateValidator, emailValidator, hexadecimal, maxDateToday, numeric and passwordValidator
). Also the directives already defined by angular like required, minLength or maxLength
can be used.
ng g component ui-timepicker
.@Component({
selector: 'ui-timepicker',
templateUrl: './ui-timepicker.html',
animations,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: UITimePickerComponent,
multi: true,
}]
})
export class UITimePickerComponent extends UIElementBase<string> {
@Input() placeholder = '';
constructor(@Optional() @Inject(NG_VALIDATORS) validators: Array<any>,
@Optional() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<any>,
validationService: ValidationService) {
super(validators, asyncValidators, validationService);
}
}
<div *ngIf="mode !== Mode.VIEW">
<label class="control-label col-sm-3" [attr.for]="id">{{ title }}{{mandatoryLabel}}:</label>
<div class="col-sm-5">
<timepicker [(ngModel)]="value" [ngClass]="{invalid: (invalid | async)}" [id]="id"></timepicker>
</div>
<div class="col-sm-4">
<ui-messages-validation
*ngIf="invalid | async"
[messages]="failures | async">
</ui-messages-validation>
</div>
</div>
<div *ngIf="mode === Mode.VIEW">
<!-- to display the component in read only mode -->
<ui-review [title]="title" [value]="value"></ui-review>
</div>
@NgModule({
...
declarations: [..., UITimePickerComponent],
exports: [..., UITimePickerComponent]
})
export class UIElementsModule {}
<form #frmName="ngForm" id="frmName" novalidate (ngSubmit)="onSubmit()" novalidate>
<div class="row">
<ui-timepicker required
dataErrorRequired="The time field is mandatory"
id="tpName"
title="{{'fields.time.title' | translate}}"
[mode]="mode"
[(ngModel)]="model"
[ngModelOptions]="{standalone: true}">
</ui-timepicker>
</div>
@Component({...})
export class MyFormComponent extends UIFormComponent {
constructor(..., validation: ValidationService) {
super(validation);
}
onSubmit() {
if (this.validate()) {
// ... continue saving data
}
}
}
Note: The component will display the invalid field with a red border and all the validator will be activated when you click on the field and leave it.
More Info:
The application include a 'Logger' service in the 'Core' module that could be implemented in different ways: Using just the console or using other system like logs into a file. To enable the application to use one or other system you have to change the class to be instanciated in the factory '/core/factories/logger.factory.ts'.
@Injectable()
export class FileLoggerService implements LoggerService {
log(msg: string) {...}
error(msg: string) {...}
warn(msg: string) {...}
}
export function loggerFactory(): LoggerService {
return new FileLoggerService(...);
}
The 'Core' module contains also a 'Simple Error Handler' that implements the interface 'ErrorHandler' present in the package '@angular/core'. Each http call that fails, automatically will call the method handleError() of the handler. In 'Simple Error Handler' the errors are displayed in different ways depending of the HTTP status code (if is a server, authentication or request errors...).
Create a new class that implement the angular interface 'ErrorHandler'.
import { Injectable, Injector, ErrorHandler } from '@angular/core';
@Injectable()
export class CustomErrorHandler implements ErrorHandler {...}
Go to the factory '/core/factories/error-handler.factory.ts' and change the class to be used during initialization:
export function errorHandlerFactory(): ErrorHandler {
return new CustomErrorHandler(...);
}
Restart the server
More Info: https://netbasal.com/angular-2-custom-exception-handler-1bcbc45c3230
Each module have a folder store where will be saved the actions (heroes.actions.ts), effects (heroes.effects.ts) and reducers (heroes.reducers.ts).
In the module class (heroes.module.ts) are imported the reducers to be called by each feature using the class StoreModule and also the EffectsModule.
export const reducers: ActionReducerMap<any> = {
heroes: heroReducer.reducer
};
@NgModule({
imports: [
...
StoreModule.forRoot(reducers),
EffectsModule.forRoot([HeroEffects])
],...
})
export class HeroesModule {}
this.store.select(isDeleted).subscribe((deleted) => {
this.actionSuccess(...);
});
this.store.select(getDeleteError).subscribe((error) => {
this.actionError(...);
});
See next diagram:
The application include a 'Not-Found' component inside the 'Shared' module that will be displayed in the case the user type an invalid route in the browser.
To check if the application have quality errors execute the following command:
npm run lint
More Info: http://blog.rangle.io/understanding-the-real-advantages-of-using-eslint/
The project have some predefined unit tests defined in the files '.spec' related of each service and component and the functional test should be implemented in the 'e2e' folder outside of the app.
npm run test
npm run e2e
More Info:
The application include a 'Cache Service' defined in /core/services/cached.service.ts
that fetch information just the first time and the rest of the time return the cached information. If I want to define a service that cache the information returned, just extends your service from the 'CacheService' class:
@Injectable()
export class EditorialService extends CachedService<Editorial> {
constructor(protected http: Http, @Inject('api.config') protected apiConfig: ApiConfig) {
super(http, apiConfig);
}
public getServiceUrl(): string {
return CommonUtil.getApiUrl('EDITORIAL_SERVICE_URL', this.apiConfig);
}
}
More Info:
npm run management:console:start: starts the management console which is accessible from http://server:2000
npm run management:console:stop: sopts the management console
Version | Tag | Published |
---|---|---|
1.0.0 | latest | 5yrs ago |