/angular-snippets

Code snippets to help the newbie like me to overcome specific problems when developing a web app with Angular

Primary LanguageTypeScriptMIT LicenseMIT

Angular Snippets

This project was generated with Angular CLI version 1.5.5 . To run this locally, see the CLI documentation section.

Every snippet is articulated around:

  • the context: a brief description of the problem statement
  • concepts: key points with code illustrations
  • demo: link to demo of concepts
  • further: if applicable, what would deserve more analysis
List of snippets:

All snippets can be tried on the demo installation

RxJs Recursive Observable

The context

You need to retrieve data with a paging API where the number of pages required all data is not known in advance. You know that you finished when:

  • number of data items retrieved is strictly smaller than the page size
  • or when you get an empty result set

Concepts

Service

The service that makes use of the paging API returns an Observable. The result type (the generic type parameter) would typically be an array of items.

Component

The caller of the service will subscribe to the returned Observable to display or process the incoming data.

Service implementation

Iterating through pages will be implemented with the Angular HttpClient. It will start from the initial page (position zero) and move up until the last.

From a single page, we:

  • get a result
  • get to know the next start at position for reading the next page
  • also find out whether the page traversal is complete.

This is encapsulated in:

class PageContext {
  startAt = 0;
  result: any = null;
  completed = false;
}

The signature of the method to read a single page is expressed as:

  processPage(ctx: PageContext): Observable<PageContext>

As a simplication, let's assume that the return type is a PageContext and not an Observable. The algorithm to traverse all pages would be something like:

while(! ctx.completed) {
    ctx = processPage(ctx);
}

... but the result type is an Observable. So the algorithm above needs to be written with Rx Operators.

To apply a basic transformation, one can use the map operator: i.e. your tranformation is purely doing basic synchronous operations. For example, if function t returns a type T, the return type of the expression below will be Observable<T>:

processPage(ctx).map( ctx => t(ctx) );

Here after reading a page, depending the status we either return what we got: a simple transformation, or, we need to read another page: an async call returning an observable. Because of the second condition we need to use the mergeMap operator. Using map would make the return type be:

// No this is not what we want :( 
Observable<Observable<PageContext>>

So the simple while loop becomes:

private iteratePages(initialCtx: PageContext): Observable<PageContext> {
  return this.processPage(initialCtx)
    .mergeMap(ctx => {
      if (ctx.completed) {
        return Observable.of(ctx);

      } else {
        return this.iteratePages(ctx);
      }
    });
}

Demo

The demo to illustrate the implementation principle computes factorial. See the factorial.service.ts implementation:

  • A delay of 500ms is introduced to mimick the latency of retrieving one page of items
  • The code is almost identical to the snippets above.

It works! But one has to wait for the full result: i.e. the implementation above yields the results until the very last iteration.

RxJs Observable reactive recursivity

The context

As in the section RxJs Recursive Observable we need to read data by iterating through pages. This time we want to issue results as soon as we get them from a page.

Concepts

We used an Observable in the previous section but nevertheless ended up with a blocking implementation. Whereas writing a loop is easy, emitting intermediary results was a strugle, until I understood the ...

RxJs expand operator

The RxJs expand operator will keep calling the function passed as an argument until it returns Observable.empty().

Let's take a close look at the ReactiveFactorialServiceImpl class, in factorial.service.ts:

factorial(n: number): Observable<number> {
  return Observable.of(new PageContext(n))
    .expand( ctx => {
      return (ctx.completed) ? Observable.empty() : this.singleOp.processPage(ctx);
    })
    .map(ctx => ctx.result);
}

From the perspective of an imperative style developer, the expand operator will:

  • emit the results down the transformation chain after every call
  • will reinject the non empty result as an input in the next call to the function in expand

Demo

Try now the demo and see how intermediary results are delivered at every step of the calculation 😄

Further

A beneficial side effect of displaying results as they arrive is that the user may want to interrupt the process:

  • maybe the user found what he needed in the first results
  • or she/he sees that this is not what she/he was after
  • browses to another page, ...

The RxJs Don't unsuscribe | Medium.com article is very pertinent in that respect. It proposes the use of the takeUntil operator. A very good illustration can be found in the Aligator.io/angular/takeuntil-rxjs-unsubscribe/ example.

HTML 5 Blob save

The context

You want to the user to save content on her/his local device. This content is typically provided by an API.

Concepts

Store downloaded content in browser

The downloaded content is saved in the browser's local storage and made available to the download to the user by creating a so called HTML 5 blob URL with the method createObjectURL.:

const xml = '<hello></hello>';
const cnt = new Blob([xml], {type: 'text/xml'});
const url = window.URL.createObjectURL(cnt);

The locator from the variable url is then included in an anchor element <a href=""> to allow the user to save the file.

Mark the download URL as safe

Angular will prevent a raw URL generated with the createObjectURL method from working if used as is by adding the prefix unsafe:. To avoid this, one has to inject the DomSanitizer utility as shown in the blob-save.component.ts component. It will generate a SafeUrl object which can then be used in the HTML view: see blob-save.component.html.

Dispose of storage space after use

The storage space needs to be freed up with the method window.URL.revokeObjectURL to avoid memory leaks. This call is made by the ngOnDestroy method in blob-save.component.ts.

Demo

Check the demo.

Further

The user experience from the demo displays the progress and the option to save on the same page. This would need to be included in a pop-up for a real app.

Mocking

The context

Because your workstation cannot reach the real implementations, you need to substitue calls to real APIs by calls to a fake API layer that will mimic the real one.

A mock layer is supposed to be simple and does not intend to replace the real thing. It is more to allow to carry on some work despite not having access to the real backend.

Concepts

Using Angular's Modules, one will encapsulate backend calls and will configure the Dependency Injection to use either the module with real implementations or the mock layer.

Demo

Please access the very impressive demo :).

What you see is a very simple string returned by a Mock implementation.

The switch between the real or mocks is done in the app.module.

This will configure the backend layer module called mocking to use the real implementation:

  imports: [
    BrowserModule,
    AppRoutingModule,
    MockingModule.forRoot()
  ],

Changing forRoot() to forMocks() will run the backend layer with mocks.

Develop and build this project

Development server

Run ng serve for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files.

Code scaffolding

Run ng generate component component-name to generate a new component. You can also use ng generate directive|pipe|service|class|guard|interface|enum|module.

Build

Run ng build to build the project. The build artifacts will be stored in the dist/ directory. Use the -prod flag for a production build.

Running unit tests

Run ng test to execute the unit tests via Karma.

Running end-to-end tests

Run ng e2e to execute the end-to-end tests via Protractor.

Further help

To get more help on the Angular CLI use ng help or go check out the Angular CLI README.

Build Status