Common Issues in Angular Development

· 8 min read

Angular development often encounters various problems. Here I will summarize the main issues I’ve faced during development, both for my occasional reference and hopefully to help others solve similar problems.

Note: *This article is continuously updated*. Due to space limitations, some code snippets are partial. For complete examples, I recommend checking GitHub-ISSUES.

Table of Contents

  1. Can’t JavaScript in [innerHTML] be executed?
  2. Subscribing to both route parameters and query parameters (params and queryParams)
  3. Handling multiple asynchronous requests in parallel
  4. Iterating over object properties with *ngFor
  5. Component class inheritance
  6. How to make component styles extend beyond the component scope
  7. Boolean type conversion for dropdown list options
  8. Template tags ,
  9. How to dynamically change content in index.html when it’s not templatized in CLI
  10. How to add third-party CSS in CLI projects
  11. Using interceptors with HttpClient

Can’t JavaScript in [innerHTML] be executed?

Answer: YES, it’s not possible. This involves Angular’s security mechanism, which is officially explained here. The Angular team’s position is that if this were supported, it would pose a security risk. Think about it - Angular itself is a JS framework, and if HTML code blocks could execute JS, it would break the framework and introduce critical security vulnerabilities.

For HTML content, if you want Angular to parse it, you need to inject the DomSanitizer service and mark the HTML as trusted. If commonly used in web applications, consider making it a pipe. See an example here.

Additionally, using bypassSecurityTrustHtml, tests show the following behavior:

  • Inline CSS works normally
  • External linked CSS works normally
  • JavaScript execution is not allowed
  • Embedded media works normally

If we still want to execute JavaScript in HTML code blocks, we need to take a different approach, like using iframes. There’s always a solution.

Subscribing to both route parameters and query parameters (params and queryParams)

When entering a view page, we often need to subscribe to parameters. There are various types of parameters: path parameters, query parameters, and anchor parameters. If we write using nested subscriptions, it would look like this:

    this.route.queryParams.subscribe(qparams => {
            alert(qparams['title']);
            this.route.params.subscribe(params => {
                alert(params['id']);
            });
        })

But such code is extremely difficult to maintain. RXJS has a solution for this:

 let obsCombined = Observable.combineLatest(this.route.params, this.route.queryParams, (params, qparams) => ({
            params,
            qparams
        }));
        obsCombined.subscribe(ap => {
            console.log(ap);
            let params = ap['params'];
            let qparams = ap['qparams'];
            alert(qparams['title']);
            alert(params['id']);
        });

This reduces nesting and makes the code much cleaner.

Note a detail: import {Observable} from "rxjs/Rx"; Writing import {Observable} from "rxjs/Observable"; will result in the error Property 'combineLatest' does not exist on type 'typeof Observable'.

Iterating over object properties with *ngFor

Sometimes you need to iterate over object properties, but Angular’s ngFor directive only supports iterating over arrays. To convert objects, consider encapsulating a pipe as follows:


import {Pipe, PipeTransform} from "@angular/core";
/**
 * Object to array
 */
@Pipe({
  name: 'keys'
})
export class KeysPipe implements PipeTransform {

  transform(value: any, args?: any): any {
    return Object.keys(value);
  }
}

This essentially uses JavaScript’s Object.keys() method, which:

The Object.keys() method returns an array of a given object’s own enumerable properties, in the same order as that provided by a for…in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well).

After creating the pipe, use it like this:

<div *ngFor="let key of facetFields|keys">
key:{{key}},
value:{{facetFields[key]}}
      </div>

Handling multiple asynchronous requests in parallel

Sometimes you need to process multiple request results simultaneously. If done with nesting, it would look like this:

this.http.get('/api/test1')
      .subscribe(res1 => {
        this.http.get('/api/test2').subscribe(res2 => {
          this.res1 = res1;
          this.res2 = res2;
        });
      });

This structure is cumbersome and hard to maintain. Using RxJS’s forkJoin operator:

const req1 = this.http.get('/api/test1');
const req2 =this.http.get('/api/test2');

Rx.Observable.forkJoin(req1, req2).subscribe(
res => {
 this.res1 = res[0];
 this.res2 = res[1];
)) 

This only notifies us when all request results are received.

Component class inheritance

Support for component inheritance was added in ng-v2.3, further improving code reusability.

Component inheritance supports:

  • Meta annotations, like @input, @output
  • Constructor
  • Component lifecycle hooks

All three support reuse. See the official explanation here

image

How to make component styles extend beyond the component scope

How to make component A’s styles available in its child component B

Besides writing styles as external CSS (global CSS), you can modify the view encapsulation mode to allow component styles to extend outward for use by other components. Just configure component A’s view mode like this:

@Component({
    selector: 'app-css',
    templateUrl: './css.component.html',
    styleUrls: ['./css.component.css'],
    encapsulation: ViewEncapsulation.None
})

With this configuration, all DOM elements under this component at any level can use these styles.

Note

Component inheritance does not support templates and styles; any behaviors that manipulate the DOM should be physically separated.

See a practical demo here

Boolean type conversion for dropdown list options

When we use ngModel two-way binding on dropdown lists, if our object is not a string type (e.g., boolean), it will be converted to a string when a user selects an option.

If non-string types are provided for dropdown options, the default value will be converted to a string. Using ngValue preserves the original type:

<select [(ngModel)]="isList" (ngModelChange)="viewTypeChanged()">
    <option [ngValue]="true">List View</option>
    <option [ngValue]="false">Group View</option>
</select>

Template tags ,

We often use ng-template with directives like ngIf:

<div *ngIf="isList;else elseBlock">
    <ul>
        <li *ngFor="let item of items">
            <a routerLink='../detail/{{item.id}}' [queryParams]="{title:item.title}">
                {{item.title}}
            </a>
        </li>
    </ul>
</div>
<ng-template #elseBlock>
    <div>
        Group View
    </div>
</ng-template>

However, this approach adds an extra div tag for the if statement, which we might not want. To avoid adding this extra div tag, use the ng-container tag:

<ng-container *ngIf="isList;else elseBlock">
    <ul>
        <li *ngFor="let item of items">
            <a routerLink='../detail/{{item.id}}' [queryParams]="{title:item.title}">
                {{item.title}}
            </a>
        </li>
    </ul>
</ng-container>
<ng-template #elseBlock>
    <div>
        Group View
    </div>
    <div *ngFor="let i of [1,2,3]">
        <h3> {{i}}</h3>
    </div>
</ng-template>

How to dynamically change content in index.html when it’s not templatized in CLI

In Angular development, most people use the official CLI build tool, but under the official CLI build, the index page is static. For example, if you want to add content to the index page during the build and packaging process, you’ll have to implement it manually.

Here’s a scenario and solution:

Scenario

For production deployment, I want to add to the built and packaged index.html page, so by looking at the source code marker, I can know when this new frontend version was released.

Solution

Add a script file

const fs = require('fs');
const cheerio = require('cheerio');
const moment = require('moment');
const indexFilePath = 'dist/index.html';

fs.readFile(indexFilePath, 'utf8', function (err, data) {
  if (err) {
    return console.log(err);
  }
  const $ = cheerio.load(data, {decodeEntities: false});
  const releaseDate = moment().format('YYYY-MM-DD HH:mm');
  $('meta').last().append(`<meta name="releaseDate" content="${releaseDate}"/>`);
  // now write that file back
  fs.writeFile(indexFilePath, $.html(), function (err) {
    if (err) return console.log(err);
    console.log('Successfully rewrote index html');
  });
});

package.json

    "build:prod": "ng build --prod && node scripts/after-build.js",

This way, when we run npm run build:prod, it will execute the content modification script after the frontend build is successful.

How to add third-party CSS in CLI projects

For third-party CSS, considering future upgrades and avoiding direct tampering with source code by developers, it’s generally not placed under assets.

For third-party CSS:

If you want to package it directly into styles.css, there are two approaches:

  1. Configure it in the JSON configuration file
 "styles": [
        "../node_modules/ngx-bootstrap/datepicker/bs-datepicker.css",
        "styles.css"
      ],
  1. Import directly in CSS
/* You can add global styles to this file, and also import other style files */
@import '~ngx-bootstrap/datepicker/bs-datepicker.css';

Between these two options, I recommend the first one. For CLI projects, prioritize using the CLI approach.

If you still want physical separation, simply import in index.html

<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">

Using interceptors with HttpClient

Angular 4.3 introduced interceptor functionality, which supports modifying requests and responses - meaning you can pre-process before sending a request and after receiving a response.

Official description:

A major feature of /http is interception, the ability to declare interceptors which sit in between your application and the backend. When your application makes a request, interceptors transform it before sending it to the server, and the interceptors can transform the response on its way back before your application sees it. This is useful for everything from authentication to logging.

Scenario

In actual development for user authentication, my frontend needs to add a token in the request header, and when receiving a backend response, execute corresponding actions for specific states.

CODE

token.interceptor.service.ts

import {Injectable} from '@angular/core';
import {AuthService} from './auth.service';
import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
import {AlertService} from './alert.service';
import 'rxjs/add/operator/do';

/**
 * Created by He on 10/01/18.
 * Token Interceptor
 */
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  constructor(public authService: AuthService, private alertService: AlertService) {
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (this.authService.getToken()) {
      request = request.clone({
        setHeaders: {
          Authorization: `Bearer ${this.authService.getToken()}`
        }
      });
    }
    return next.handle(request).do((event) => {
        return event;
      },
      (err: any) => {
        if (err instanceof HttpErrorResponse) {
          if (err.status === 401) {
            this.alertService.showAlert({type: 'danger', msg: err.error['err']});
            this.authService.removeToken();
            location.reload();
          }
        }
        return Observable.throw(err);

      }
    );
  }
}

Out of Memory During Build

Solution: specify NODE memory

    "build": "node --max-old-space-size=8192 $(which ng) build",

Still have questions??? Not detailed enough???

Still have questions???

Welcome to submit tickets and check the latest answers to the above questions on GitHub/angular-demo Click here