Join the social network of Tech Nerds, increase skill rank, get work, manage projects...
 
  • How to Manage Subscriptions in Angular 4 App with Async Pipe

    • 0
    • 0
    • 0
    • 0
    • 0
    • 0
    • 1
    • 0
    • 688
    Comment on it

    The async pipe is a special kind of pipe that automatically subscribes to an observable in order to display the emitted values. With this pipe manual subscription and unsubscription to observable in component classes can be avoided. In angular once subscription is done, it keeps working until it is unsubscribed.

     

     

     

    let's learn with example:

     

    Example

    Consider an observable returning current time after every second.

    •  When no async pipe is used, for observables manual subscription is performed with no unsubscription.

    Consider Parent Component "AppComponent" and two child components "HomeComponent" and "TimeComponent".

     

    time.component.ts

    import { Component, OnInit } from '@angular/core';
    import { Router } from '@angular/router';
    import { Observable } from 'rxjs';
    import 'rxjs/add/observable/interval';
    import 'rxjs/add/operator/map';
    
    @Component({
     selector: 'app-time',
     template: `<a [routerLink]="['/home']" class="btn btn-link">Home</a><div>Time: {{ currentTime | date:'mediumTime' }}</div>`
    })
    export class TimeComponent implements OnInit {
     currentTime: Date;
    
     constructor(private router:Router) { }
    
     ngOnInit() {
     Observable
       .interval(1000)
       .map(val => new Date())
       .subscribe(val =>{
        this.currentTime = val,
        console.log(this.currentTime)
       });
     }
    }

     

    In above TimeComponent a member variable "currentTime"  with Date type is defined. Within OnInit hook an observable is created which is emiting a value every second and this value is being mapped to a new date. Subscription is performed to this observable and currentTime class variable is set to the emitted value. In the component template, built-in date pipe is used for transforming the date into desired format of minutes and seconds.

     

    home.component.ts

    import { Component, OnInit } from '@angular/core';
    
    @Component({
     selector: 'app-home',
     template: `<div><a [routerLink]="['/time']" class="btn btn-link">Time</a></div>This the Home Component`,
    })
    export class HomeComponent implements OnInit {
    
     constructor() { }
    
     ngOnInit() {
     }
    }

     

    It is a simple component which is navigating to time component.

     

    app.component.ts (Parent Component)

    import { Component} from '@angular/core';
    
    @Component({
     selector: 'app-root',
     template: `<router-outlet></router-outlet>`
    })
    export class AppComponent{
    
    }

     

    Navigations are provided in "app.routing.ts"

    import { Routes, RouterModule } from '@angular/router';
    import { NgModule } from '@angular/core';
    import { HomeComponent } from './home/home.component';
    import { TimeComponent } from './time/time.component';
    
    const routes: Routes = [
     {
      path: '',
      redirectTo: 'home',
      pathMatch: 'full'
     },
     {
      path: 'time',
      component: TimeComponent,
     },
     {
      path: 'home',
      component: HomeComponent,
     },
    ];
    @NgModule({
     imports: [RouterModule.forRoot(routes)],
     exports: [RouterModule]
    })
    export class AppRoutingModule { }

     

    app.module.ts (Parent Module)

    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { AppComponent } from './app.component';
    import { HomeComponent } from './home/home.component';
    import { AppRoutingModule } from './app.routing';
    import { TimeComponent } from './time/time.component';
    
    @NgModule({
     declarations: [
      AppComponent,
      HomeComponent,
      TimeComponent
     ],
     imports: [
      BrowserModule,
      AppRoutingModule,
     ],
     providers: [],
     bootstrap: [AppComponent]
    })
    export class AppModule { }

     

    index.html

    <!doctype html>
    <html lang="en">
    <head>
     <meta charset="utf-8">
     <title>AngularDemo</title>
     <base href="/">
     <meta name="viewport" content="width=device-width, initial-scale=1">
     <link rel="icon" type="image/x-icon" href="favicon.ico">
     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css">
    </head>
    <body>
     <app-root></app-root>
    </body>
    </html>

     

    When application runs, the application is navigated to default Home route.

     

    Output

     

    When "Time" is clicked it it navigated to TimeComponent which is displaying current time. In console current time is printed every second.

     

    Output


    Now when user navigates to HomeComponent through "Home" link, even though user cannot see current time every second appear in view, current time is still printed to console when in Home component. 

     

    Output

     

    This is due to subscription to observable is not unsubscribed, and if unsubscription is not performed can cause a memory leak.


    To stop above activity with observables manual subscription is to be performed.

    • Manual Subscriptions of Observables with unsubscription.

     

    time.component.ts is changed to

    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { Router } from '@angular/router';
    import { Observable } from 'rxjs';
    import { Subscription } from 'rxjs/Subscription';
    import 'rxjs/add/observable/interval';
    import 'rxjs/add/operator/map';
    
    @Component({
     selector: 'app-time',
     template: ` <a [routerLink]="['/home']" class="btn btn-link">Home</a><div>Time: {{ currentTime | date:'mediumTime' }}</div>`
    })
    export class TimeComponent implements OnInit, OnDestroy {
     currentTimeSub: Subscription;
     currentTime: Date;
    
     constructor(private router:Router) { }
    
     ngOnInit() {
     this.currentTimeSub = Observable
       .interval(1000)
       .map(val => new Date())
       .subscribe(val =>{
        this.currentTime = val,
        console.log(this.currentTime)
       });
     }
     
     //To unsubscribe when the component is disposed of
     ngOnDestroy(){
      this.currentTimeSub.unsubscribe();
     }
    }

     

    The component above only defines an subscripted member variable "currentTimeSub" which receives Subscriber from observable, which when component is destroyed is unsubscribed.

     

    In below output it can be seen that when from time component, home component is navigated, in console contains only those time from where time component was navigated to home component.

     

    Output

     

     

    • Using async pipe

     

    time.component.ts is changed to

    import { Component, OnInit } from '@angular/core';
    import { Router } from '@angular/router';
    import { Observable } from 'rxjs';
    import 'rxjs/add/observable/interval';
    import 'rxjs/add/operator/map';
    
    @Component({
     selector: 'app-time',
     template: ` <a [routerLink]="['/home']" class="btn btn-link">Home</a><div>Time: {{ currentTime | async | date:'mediumTime' }}</div>`
    })
    export class TimeComponent implements OnInit {
     currentTime: Observable<Date>;
    
     constructor(private router:Router) { }
    
     ngOnInit() {
     this.currentTime = Observable
       .interval(1000)
       .map(val => new Date())
     }
    }


    In above TimeComponent an observable member variable "currentTime" is defined and there is no direct access to the data at the component level only the component template accesses the data. Whenever async pipe is used with observable member variable, it automaticaly subscribe and unsubscribe when the component is destroyed.


    Async Pipe with ngFor and ngIf

    Create another component "StudentComponent"

     

    student.component.ts

    import { Component,OnInit } from '@angular/core';
    import { Observable } from 'rxjs';
    
    @Component({
      selector: 'app-student',
      templateUrl: './student.component.html'
    })
    export class StudentComponent implements OnInit {
     students:any;
     constructor() { }
    
      ngOnInit() {
       this.students = Observable.of([
       {name: 'Kapil', branch: 'Computer'},
       {name: 'Himanshu', branch: 'Electrical'},
       {name: 'Somdev', branch: 'Computer'},
      ])
     }
    }

     

    using ngFor and ngIf

    student.component.html

    <div *ngIf="students | async">
     <ul>
      <li *ngFor="let student of students | async">
       Name: {{ student.name }},
       Branch: {{ student.branch }}
      </li> 
     </ul>
    </div>

     

    Since async pipe handles subscription and unsubscription by itself. Multiple async on same observable member variable causes multiple subscription(means multiple HTTP requests in case of HTTP requests) which may cause problem. To avoid this following template can be used

     

    <div *ngIf="students | async; let studentlist">
     <ul>
      <li *ngFor="let student of studentlist">
       Name: {{ student.name }},
       Branch: {{ student.branch }}
      </li> 
     </ul>
    </div>

    In above template the expression used in ngIf causes a local variable studentlist available inside the ngIf section, corresponding to the value emitted by the backend call avoiding multiple subscription.

     

    Following files are changed for StudentComponent

    app.routing.ts

    import { Routes, RouterModule } from '@angular/router';
    import { NgModule } from '@angular/core';
    import { HomeComponent } from './home/home.component';
    import { TimeComponent } from './time/time.component';
    import { StudentComponent } from './student/student.component';
    
    const routes: Routes = [
     {
      path: '',
      redirectTo: 'home',
      pathMatch: 'full'
     },
     {
      path: 'time',
      component: TimeComponent,
     },
     {
      path: 'home',
      component: HomeComponent,
     },
     {
      path: 'student',
      component: StudentComponent,
     },
    ];
    @NgModule({
     imports: [RouterModule.forRoot(routes)],
     exports: [RouterModule]
    })
    export class AppRoutingModule { }

     

    app.module.ts

    
    
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { AppComponent } from './app.component';
    import { HomeComponent } from './home/home.component';
    import { AppRoutingModule } from './app.routing';
    import { TimeComponent } from './time/time.component';
    import { StudentComponent } from './student/student.component';
    
    @NgModule({
     declarations: [
      AppComponent,
      HomeComponent,
      TimeComponent,
      StudentComponent
     ],
     imports: [
      BrowserModule,
      AppRoutingModule,
     ],
     providers: [],
     bootstrap: [AppComponent]
    })
    export class AppModule { }

    When above application runs with route to "/student"

     

    Output

     

     

     

    How to Manage Subscriptions in Angular 4 App with Async Pipe

 0 Comment(s)

Sign In
                           OR                           
                           OR                           
Register

Sign up using

                           OR                           
Forgot Password
Fill out the form below and instructions to reset your password will be emailed to you:
Reset Password
Fill out the form below and reset your password: