My learning curve with Angular
--
Introduction 🖊:
Hello everyone, this a blog of my “blogging challenge 2020”, I will write about my learning curve with angular , some tricks that i tried to implement with angular and thinks that should be shared with community , I just tried to make some advancement implementation and check different use cases with different angular modules .
Happy learning :)
1) Recommended Angular project structure : ⛑
Before start coding, we should set the best-recommended project structure from the community, from a lot of blogs that I have read and recommended GitHub repositories, I think this is the most commonly used project structure
- Modules:
This is the folder dedicated to features implemented inside the application, for example, if you have an Authentication module to handle all the (sign-in) and (signup feature), Module: Dashboard if you have feature Dashboard management, they can all be set under the folder “Modules” or “Features”
- Core:
⚡ The core module is dedicated to containing all elements that continue to core like services, interceptors , guards , mocks , helpers , utils etc ..
- ⚠️ The core module must be imported only in the root module. Other modules must not import the core modules. You can use the following code to stop the other modules from importing the core module
@NgModule({})export class CoreModule {constructor(@Optional() @SkipSelf() core:CoreModule ){if (core) {throw new Error("You should import core module only in the root module")}}}
- Shared :
The shared module must declare the pipes, directives, services and alll the components and modules shared among different pages, such as a footer or the Material modules. You must declare them or import them in the _shared module, as well as export them.
@NgModule({imports: [CommonModule,FormsModule,ReactiveFormsModule,HttpClientModule,RouterModule,NgbModule,],exports: [CommonModule,FormsModule,ReactiveFormsModule,HttpClientModule,RouterModule,components,],declarations: [components, pipes, directives],providers: [],})export class SharedModule {}
2) Lazy Loading with Angular: ▶️ ◀️
So if you plan to implement a large scale application, it’s worthless to implement the lazy loading pattern, before to start coding you need to divide your features into modules and make some hierarchy for your application
Utility? ✔️✔️ ✔:
- To improve the performance
- Generates a better user experience by shortening load times and only loading necessary info.
Implementation:
This is an example of a lazy loading implementation feature with Angular10
export const routes: Routes = [{ path: 'admin', loadChildren: () => import(`./admin/admin.module`).then(m =>m.AdminModule) },{ path: 'auth', loadChildren: () => import(`./authentication/authentication.module`).then(m => m.AuthenticationModule) },{path: '**',redirectTo: 'auth'}]
3) TypeScript module resolution with AngularCLI:🛠
This section dedicated to explaining the module resolution strategy that we can implement with our angular project .
You need to add paths to your tsconfig.json to make it easy to import your modules.!
"paths": {"@features/*": ["src/modules/*"],"@core/*": ["src/app/core/*"],"@shared/*": ["src/app/shared/*"],"@configs/*": ["src/configs/*"],"@angular/core/src/metadata/*": ["./node_modules/@angular/core"]}
4) Sub routing : 🏹🏹
In this section, I will show you a basic example of navigation form component A in module A+ to component B in Module B+
What is Sub-routing :
Routing means navigating between the pages. , Sub-routing or Child-routing means navigation between sub-modules or form child route to another,
const routes: Routes = [{path: '',redirectTo: 'dashboard',},{path: 'dashboard',component: DashboardComponent,children: [{path: 'test',component: DashboardComponent,},],},];
In our application, we will have a main root ( root route), and other routes for each module for its own children (component of course )
Implementation:
This example fo routes definition in each module
Routing Authentication Module :
import { NgModule } from '@angular/core';import { Routes, RouterModule } from '@angular/router';import { LoginComponent } from './login/login.component';import { SignupComponent } from './signup/signup.component';const routes: Routes = [{ path: '', component: LoginComponent },{ path: 'signup', component: SignupComponent }];@NgModule({imports: [RouterModule.forChild(routes)],exports: [RouterModule]})export class AuthenticationRoutingModule { }
Routing Admin Module :
import { NgModule } from '@angular/core';import { Routes, RouterModule } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';const routes: Routes = [{path: '',redirectTo: 'dashboard',},{path: 'dashboard',component: DashboardComponent,children: [{path: 'test',component: DashboardComponent,},],},];@NgModule({imports: [RouterModule.forChild(routes)],exports: [RouterModule],})export class AdminRoutingModule {}
Task: navigation from sign-up — -> dashboard?
Solution :
this._router.navigate([‘admin/dashboard’]);
5) Advanced Interceptors to our Angular project:📞
What is an interceptor :
Interceptor is a way to add some work or modification for each HTTP request or response.
There are a lot of uses cases that I discovered and it looks interesting to me that’ why I want to share here :
- HTTP Error Handler : 💥 💥
First, we can retry the HTTP call.
constructor(private toastr: ToastrService) {}intercept(req: HttpRequest<any>,next: HttpHandler): Observable<HttpEvent<any>> {if (!req.url.includes('error')) {return next.handle(req);}console.warn('ErrorInterceptor');return next.handle(req).pipe(retry(2),
Secondly, we can check the status of the exception. And depending on the status, we can decide what we should do.
catchError((error: HttpErrorResponse) => {if (error.error instanceof ErrorEvent) {console.error('An error occurred:', error.error.message);} else {switch (error.status) {case 400:console.log('400 Error Server !');break;case 405:console.log('405 Error Server !');break;case 500:console.log('500 Error Server !');break;case 403:console.log('403 Error Server !');break;default:console.log('Unknown Error Server !');}}return throwError(error);})
- Converting CSV TO JSON : 💥 💥
This an example of an Interceptor that will convert from some HTTTP Calls that will get a CSV format response and you will need it to display it JSON, so let’s intercept it!
import { Injectable } from '@angular/core';import {HttpInterceptor,HttpEvent,HttpHandler,HttpRequest,HttpResponse,} from '@angular/common/http';import { Observable } from 'rxjs';import { map } from 'rxjs/operators';@Injectable()export class ConverterInterceptor implements HttpInterceptor {intercept(req: HttpRequest<any>,next: HttpHandler): Observable<HttpEvent<any>> {if (!req.url.includes('/api/csv')) {return next.handle(req);}console.warn('ConvertInterceptor');return next.handle(req).pipe(map((event: HttpEvent<any>) => {if (event instanceof HttpResponse) {// --Convert body from csv to jsonconst jsonData = this.CsvToJSON(event.body);const modifiedBody = event.clone({ body: jsonData });return modifiedBody;}}));}CsvToJSON(csv) {const lines = csv.split('\n');const result = [];const headers = lines[0].split(',');for (var i = 1; i < lines.length; i++) {const obj = {};const currentLine = lines[i].split(',');for (let j = 0; j < headers.length; j++) {obj[headers[j]] = currentLine[j];}result.push(obj);}return JSON.stringify(result);}}
- Caching: 💥
Another cool use case is HTTP response caching ,
import { Injectable } from '@angular/core';import {HttpInterceptor,HttpEvent,HttpHandler,HttpRequest,HttpResponse,} from '@angular/common/http';import { Observable, of } from 'rxjs';import { tap } from 'rxjs/operators';@Injectable()export class CacheInterceptor implements HttpInterceptor {private cache = new Map<string, any>();intercept(req: HttpRequest<any>,next: HttpHandler): Observable<HttpEvent<any>> {if (!req.url.includes('/api/cache')) {return next.handle(req);}console.warn('CacheInterceptor');if (req.method !== 'GET') {return next.handle(req);}const cachedResponse = this.cache.get(req.url);if (cachedResponse) {return of(cachedResponse);}return next.handle(req).pipe(tap((event) => {if (event instanceof HttpResponse) {this.cache.set(req.url, event);}}));}}
7) Auth Guard :
In this section, I will try to explain some basic examples of the ‘Auth Guard ‘ concept used to check access and authorization for a route based on a role!
There are five different types of Guards:
- CanActivate: checks to see if a user can visit a route.
- CanActivateChild: checks to see if a user can visit a route’s children.
- CanDeactivate: checks to see if a user can exit a route.
- Resolve: performs route data retrieval before route activation.
- CanLoad: checks to see if a user can route to a module that is lazy loaded.
canActivateChild(next: ActivatedRouteSnapshot,state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {// redirect and return falseif (!this.auth.isSuperAdmin) {this.router.navigate(['']);return false;}return true;}
And the next step is to Add it to the routing module :
const routes: Routes = [{path: '',redirectTo: 'dashboard',},{path: 'dashboard',component: DashboardComponent,children: [{path: 'super-user',component: SuperUserComponent,canActivateChild: [AdminGuard],},],},];
8) Rxjs 📕 :
In this section, I will explain some Rxjs used operators that are so helpful for javascript developers and I thinks we should care about this powerful library
Whats is Rxjs 📕 :
RxJS is a javascript library that brings the concept of “reactive programming” to the web.
Some Rxjs operations:
- Observable :
“An observable is a function that creates an observer and attaches it to the source where values are expected, for example, clicks, mouse events from a dom element or an HTTP request, etc.”
- You can often use observables instead of promises to deliver values asynchronously.
Example implementation :
search(term:string): Observable<SearchItem[]> {
let apiURL = `${this.apiRoot}?term=${term}&media=music&limit=20`;
return this.http.get(apiURL)
}
- forkJoin :
This operator is best used when you have a group of observables and only care about the final emitted value of each.
image houni
Example implementation:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { forkJoin } from 'rxjs'; // RxJS 6 syntax
@Injectable()
export class DataService {
constructor(private http: HttpClient) { }
public requestDataFromMultipleSources(): Observable<any[]> {
let response1 = this.http.get(requestUrl1);
let response2 = this.http.get(requestUrl2);
let response3 = this.http.get(requestUrl3);
// Observable.forkJoin (RxJS 5) changes to just forkJoin() in RxJS 6
return forkJoin([response1, response2, response3]);
}
}
- retry:
-Useful for retrying HTTP requests!
- If you want to repeat FAILED data fetching wrapped in observable — use retry.
Implementation :
import { Http } from '@angular/http';import { Injectable } from '@angular/core';import { Observable } from 'rxjs/Rx';@Injectable()export class SearchService {constructor(private http: Http) {}search(term: string) {let tryCount = 0;return this.http.get('https://api.spotify.com/v1/dsds?q=' + term + '&type=artist').map(response => response.json()).retry(3);}}
- catch / catchError:
CatchError is an RxJs Operator, which we can use to handle the errors thrown by the Angular Observable.
@Injectable({
providedIn: 'root'
})
export class ApiService {
private SERVER = "http://server.com/api/products";
constructor(private httpClient: HttpClient) { }
handleError(error: HttpErrorResponse) {
let errorMessage = 'Unknown error!';
if (error.error instanceof ErrorEvent) {
// Client-side errors
errorMessage = `Error: ${error.error.message}`;
} else {
// Server-side errors
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
window.alert(errorMessage);
return throwError(errorMessage);
}
public fetchData(){
return this.httpClient.get(this.SERVER).pipe(catchError(this.handleError));
}
}
9) Never forgot to write Clean Code : ❎
Write readable code. Focus on writing code that is easy to understand.
Separation of concerns. Encapsulate and limit logic inside your components, services, and directives. Each file should only be responsible for a single functionality.
Utilize TypeScript. TypeScript provides advanced autocompletion, navigation, and refactoring. Having such a tool at your disposal shouldn’t be taken for granted.
Use TSLint. TSLint checks if TypeScript code complies with the coding rules in place. Combined with Prettier and Husky makes for an excellent workflow.
Next Step Add state management to my App?
In the Next Blog, I will talk about State Management with Angular application: concepts, advantages, implementations, best tools, and some discussion about when we should add state management to my app.
Conclusion :
In conclusion
“Develop a passion for learning. If you do, you will never cease to grow.”
You can find the Github repository called learning-angular-advanced ✌️✌️