Angular recommendations and good practices

Angular recommendations and good practices

Configuration Files:

tsconfig.json

Path Alias:

They are shortcuts for our files, with them we can implement imports in a more comfortable and short way.

"paths":{  
   "@src/*":[  
      "src/*"
   ],
   "@assets/*":[  
      "src/assets/*"
   ],
   "@shared/*":[  
      "src/app/shared/*"
   ]
}

angular.json

stylePreprocessorOptions:

It expects a json with the following property “includePaths” that declares shortcuts for our style files in the same that path alias, the only difference is that it doesn’t need an “@” to use it on your scss files.

"stylePreprocessorOptions": {
   "includePaths": ["src/app/shared/styles"]
 },

Configurations:

Each one of the configurations available for our project to use on our compilation.

I recommend the following for development and production:

"configurations": {
            "local": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.local.ts"
                }
              ],
              "optimization": false,
              "outputHashing": "all",
              "sourceMap": true,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": false,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                }
              ]
            },
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                }
              ]
            }
}

Structure:

Barrels:

To organize our project imports we are going to use the concept of “Barrels”, the main idea is to add a file called “index.ts” to each one of our folders that contain an .ts file. Inside of it we are going to export each exportable variable to the outside of all the .ts files creating this way a hidden hierarchy which is going to help us hiding long deep paths at our imports.

Example:

Index.ts inside “components” folder

export * from './component-folder-1';
export * from './component-folder-2';

Import at module

import { 
   Component1, 
   Component2, 
   Component3, 
   Component4 
} from './component-folder';

Please check that there are more components than the number of exports at the index.ts file, this is happening because inside of each one of those paths that we are exporting there are more index.ts files that are at the same time exporting more paths.

As you can see this is cleaner and at the same time easier to implement, we reduce the number of lines that we need to import files and also we are shortening paths.

Main Structure:

Apart from the app.module, app.component, etc. we are going to create a folder for each one of our modules, inside of it we are going to find the following structure:

No alt text provided for this image

The main idea is to separate logic by categories, each module containing its own, to reinforce the concept of lazy loading of modules where each one is going to be loaded at need and have to be independent of each other.

For those components, services, etc. that we need at more than one module (example: Angular material modules), we are going to create a “shared module” to import and export them across the app.

For shared services i recommend implementing them directly at the app.module, if we do it at the shared module we are creating a different instance of the service for each one of them, making impossible the communication using this service across the app.

Core Service:

The core or utility service file is a shared service implemented at the app.module that contains all the methods that can be reused across the app. Remember that each function has to be declarative, without side effects and has to return a value with the result, if you need to modify a parameter please create a variable with the value of it to do so.

Spinner implementation:

I recommend using an observable to implement an spinner, that can receive a boolean to show or hide it.

core.service.ts

import { Injectable } from '@angular/core';
import { AbstractControl } from '@angular/forms';
import { MatAutocomplete, MatAutocompleteSelectedEvent, MatChipInputEvent } from '@angular/material';
import { Subject } from 'rxjs';
export interface LoaderState {
  show: boolean;
}

@Injectable({
  providedIn: 'root'
})
export class CoreService {
  spinnerStatusState = new Subject<any>();


  constructor() {}


  displaySpinner(value: boolean) {
    this.spinnerStatusState.next(<LoaderState>{ show: value });
  }
}
  }

app.component.ts

import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { CoreService } from './shared/services/core.service';


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  show = false;


  constructor(private coreService: CoreService) {}


  ngOnInit() {
    this.coreService.spinnerStatusState.subscribe(state => {
      setTimeout(() => {
        this.show = state.show;
      }, 0);
    });
  }
}


app.component.html

<ng-container><span *ngIf="show"><app-spinner></app-spinner></span>
    <app-navbar> <router-outlet></router-outlet> </app-navbar>
</ng-container>

spinner.component.ts

import { Component, OnInit } from '@angular/core';


@Component({
  selector: 'app-spinner',
  template: `
    <div class="loading"></div>
  `,
  styleUrls: ['./spinner.component.scss']
})
export class SpinnerComponent implements OnInit {
  constructor() {}


  ngOnInit() {}
}


Implement your spinner scss to style your own or replace the template with a gif or other.

How to use it:

Inject the core service at your component’s constructor and before calling an endpoint do:

this.coreService.displaySpinner(true);

After the getting the result and even if it’s successful or not:

this.yourService.getInformation().subscribe(
  response => {
   ...
   this.coreService.displaySpinner(false);
  },
  error => {
   ...
   this.coreService.displaySpinner(false);
  }
);

Observables:

Combine the observables !!, there are sometime at your component that you are calling more than one endpoint at a time….instead of making a subscribe to each one of them why not just combining them ?.

I recommend using a combineLatest from rxjs to do it, these way even if some of the values don't come we are still going to enter the result or error of the subscribe and when the missing value comes we are going to maintain the last one.

import { combineLatest } from 'rxjs';

...

ngOnInit() {
  combineLatest(
    this.yourService.getObservable1(),
    this.yourService.getObservable2()
  ).subscribe(
    ([result1, result2]) = {
      ...
    }
  ) 
}

These way we can unsubscribe easier at the ngOnDestroy :)

Recommendation for calling endpoints !!!!, if your know that your observable is going to get information from the back only once, use .pipe(first()) before you subscribe, this way it’s going to get the information and unsubscribe automatically.

import { first } from 'rxjs/operators';

...

this.yourService.getObservable().pipe(first()).subscribe(...);

Custom Management of App State:

We recommend using the factory pattern to create, manage and destroy observables on the go !

I created this really cool shared service to do so and i'm sharing it with you :)

observable.service.ts

import { BehaviorSubject } from 'rxjs';
import { Injectable } from '@angular/core';


export interface ObserverArrayItem {
  key: string;
  observable: BehaviorSubject<any>;
}


@Injectable({
  providedIn: 'root'
})
export class ObservableService {
  private observerArray: ObserverArrayItem[] = [];


  createObservable(key: string) {
    const observable = new BehaviorSubject(null);
    this.observerArray.push({ key, observable });
  }


  getObservable(key: string) {
    const observableArrayItem = this.observerArray.find(obs => obs.key === key);
    return observableArrayItem.observable;
  }


  emitValue(key: string, data: any) {
    const observableArrayItem = this.observerArray.find(obs => obs.key === key);
    observableArrayItem.observable.next(data);
  }


  destroyObservable(key: string) {
    const selectedObservable = this.observerArray.find(obs => obs.key === key);
    selectedObservable.observable.unsubscribe();
    this.observerArray = this.observerArray.filter(obs => obs.key !== key);
  }
}

How to use:

The component that is going to emit data across the observable is going to be the one in charge of creating and destroying it:

On your component

export const NameOfYourObservable = 'observable1';
...
ngOnInit() {
  this.observableService.createObservable(NameOfYourObservable);
}

...

// after getting the information

this.observableService.emitValue(NameOfYourObservable, 'Hi! im the information !!');


// when we destroy our component
ngOnDestroy() {
  this.observableService.destroyObservable(NameOfYourObservable);
}

Why manage the observable life cycle at the component that emits the data??

Because if the component that emits the data is no longer there….why continue having an observable that is not going to do anything anymore ? :)

Other way of using it, is having the parent of the components do the creation and destroy of the observable, this way we can rest assured that the observable is going to be ready for its children and when the parent is gone, the observable too.

At your subscribing component

import { NameOfYourObservable } from './component-emitter-forlder';

...

ngOnInit() {
  this.observableService(NameOfYourObservable).subscribe(...)
}

And that's it! Those are some of the tips i can give you !! please share if you like it and i'm open for questions. Thank you all!

要查看或添加评论,请登录

Alan Buscaglia的更多文章

  • Angular 19: Novedades y Explicaciones

    Angular 19: Novedades y Explicaciones

    ?Hola a todos! Quiero compartir con ustedes lo último que el equipo de Angular ha anunciado sobre la versión 19. Estoy…

    13 条评论
  • Scope Rule | Regla Del Alcance

    Scope Rule | Regla Del Alcance

    -- English, Spanish after -- Hello everyone! ?? Today, I want to share a technique I've developed to organically…

    19 条评论
  • The Impact of Keyboard Switches on Streaming and Content Creation

    The Impact of Keyboard Switches on Streaming and Content Creation

    English (Espa?ol a continuación): As a streamer, content creator, Microsoft MVP, and Google Developer Expert in…

  • Angular Material as a base for reusable components

    Angular Material as a base for reusable components

    Hello everyone ! let's talk about Angular Material, a super cool UI library created by google that we angular…

  • AngularJs Basics

    AngularJs Basics

    Hi every one ! Im here to help you guys understand AngularJs with this easy introduction to it, we are going to talk…

  • $scope.$apply() When to use it ??

    $scope.$apply() When to use it ??

    Hello developers, let's talk about something you use everyday but..

  • Angular-js After Image Load Directive

    Angular-js After Image Load Directive

    Hi again! this time i bring you a really good angular-js directive i have been doing, is really useful if you want to…

  • SASS Good Classes and Tips Style Sheet

    SASS Good Classes and Tips Style Sheet

    Hi everyone !, i created a good repo with a SCSS style sheet that i believe is going to be really helpful, is a…

社区洞察

其他会员也浏览了