Categories
Angular JavaScript TypeScript

Advanced Usage of the Angular HTTP Client

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we’ll look at how to make PUT and DELETE requests with the Angular HTTP Client and using HTTP interceptors.

Making a DELETE Request

We can use the delete method to make a DELETE request by passing in the request URL as the first argument and an request options object as the 2nd argument

For example, we can use it as follows:

app.component.ts :

import { Component } from "@angular/core";  
import { HttpClient, HttpHeaders } from "@angular/common/http";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  constructor(private http: HttpClient) { } ngOnInit() {  
    this.getPost();  
  } 

  getPost() {  
    const httpOptions = {  
      headers: new HttpHeaders({  
        "Content-Type": "application/json"  
      })  
    }; 

    this.http  
      .delete("https://jsonplaceholder.typicode.com/posts/1", httpOptions)  
      .subscribe(res => console.log(res));  
  }  
}

In the code above, we call the delete method with the URL and httpOptions .

Then we call subscribe to initiate the request. The subscribe callback’s parameter has the response content.

Making a PUT Request

A PUT request can be made with the put method. It’s similar to the post method except it makes a PUT request.

The first argument is the URL, the 2nd is the request body, and the 3rd argument is the request options object.

For example, we can write the following:

app.component.ts :

import { Component } from "@angular/core";  
import { HttpClient, HttpHeaders } from "@angular/common/http";  
import { Post } from "./post";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls:["./app.component.css"]  
})  
export class AppComponent {  
  constructor(private http: HttpClient) {}  

  ngOnInit() {  
    this.addPost();  
  } 

  addPost() {  
    const body = {  
      userId: 1,  
      title: "title",  
      body: "body"  
    }; 

    const httpOptions = {  
      headers: new HttpHeaders({  
        "Content-Type": "application/json"  
      })  
    }; 

    this.http  
      .put<Post>(  
        "https://jsonplaceholder.typicode.com/posts/",  
        body,  
        httpOptions  
      )  
      .subscribe(res => console.log(res));  
  }  
}

HTTP Interceptors

We can create and use HTTP interceptors to inspect and transform HTTP requests from our app to the server.

They can also be used to inspect and transform the server’s response on their way back to the app.

Multiple interceptors form a forward and backward chain of request/response handlers.

We can create and use our interceptor as follows:

http-req-interceptor.ts :

import { Injectable } from "@angular/core";  
import {  
  HttpEvent,  
  HttpInterceptor,  
  HttpHandler,  
  HttpRequest  
} from "@angular/common/http";
import { Observable } from "rxjs";

@Injectable()  
export class HttpReqInterceptor implements HttpInterceptor {  
  intercept(  
    req: HttpRequest<any>,  
    next: HttpHandler  
  ): Observable<HttpEvent<any>> {  
    return next.handle(req);  
  }  
}

app.module.ts :

import { BrowserModule } from "@angular/platform-browser";  
import { NgModule } from "@angular/core";  
import { HttpClientModule, HTTP_INTERCEPTORS } from "@angular/common/http";  
import { AppComponent } from "./app.component";  
import { HttpReqInterceptor } from "./http-req-inteceptor";

@NgModule({  
  declarations: [AppComponent],  
  imports: [BrowserModule, HttpClientModule],  
  providers: [  
    { provide: HTTP_INTERCEPTORS, useClass: HttpReqInterceptor, multi: true }  
  ],  
  bootstrap: [AppComponent]  
})  
export class AppModule {}

In the code above, we created the HttpReqInterceptor class that extends the HttpInterceptor interface.

To implement the interface, we implement the intercept method which returns the observable returned by next.handle .

Then we can use it by adding the following:

{ provide: HTTP_INTERCEPTORS, useClass: HttpReqInterceptor, multi: true }

to the providers array in AppModule .

Interceptor Order

If we have interceptors A, B, and C provided in that order, then if we make an HTTP request, A runs, then B, then C.

When our app receives a response, it’ll run C first, then B, then A.

intercept and handle return HttpEvent<any> instead of HttpResponse<any> .

This is because interceptors examine and modify the response from next.handle .

Immutability

HttpRequest and HttpResponse instance properties are readonly , which makes them immutable.

This is good because requests may be retried several times. We don’t want the retry request to be modified before it’s retried.

Therefore, if we want to change the request, we have to clone it and then return next.handle with the cloned request passed in as follows:

http-req-inteceptor.ts :

import { Injectable } from "@angular/core";  
import {  
  HttpEvent,  
  HttpInterceptor,  
  HttpHandler,  
  HttpRequest  
} from "@angular/common/http";import { Observable } from "rxjs";

@Injectable()  
export class HttpReqInterceptor implements HttpInterceptor {  
  intercept(  
    req: HttpRequest<any>,  
    next: HttpHandler  
  ): Observable<HttpEvent<any>> {  
    const secureReq = req.clone({  
      url: req.url.replace("http://", "https://")  
    });  
    return next.handle(secureReq);  
  }  
}

In the code above, we modified all request URLs that started with http to start with https .

Modifying the Request Body

We can modify the request body as follows before sending it to the server:

http-req-interceptor.ts :

import { Injectable } from "@angular/core";  
import {  
  HttpEvent,  
  HttpInterceptor,  
  HttpHandler,  
  HttpRequest  
} from "@angular/common/http";import { Observable } from "rxjs";

@Injectable()  
export class HttpReqInterceptor implements HttpInterceptor {  
  intercept(  
    req: HttpRequest<any>,  
    next: HttpHandler  
  ): Observable<HttpEvent<any>> {  
    const body = req.body;  
    const newBody = { ...body, title: body.name.trim() };  
    const newReq = req.clone({ body: newBody });  
    return next.handle(newReq);  
  }  
}

In the code above, we copied the request body with:

const body = req.body;  
const newBody = { ...body, title: body.name.trim() };

Then we set the body of the request to the newBody by writing:

const newReq = req.clone({ body: newBody });

In the end, we returned the request observable.

We set the body to an empty object undefined or null to clear the request body in the example above.

Conclusion

We can make PUT and DELETE requests by calling the put and delete methods.

put takes the URL as the first argument, the request body as the 2nd argument, and the request options object as the last argument.

delete takes the URL as the first argument, and the request options object as the last argument.

To transform requests before sending it, we can create and user HTTP interceptors.

They can be chained and called in the same order they’re provided for making requests, and the reverse order when receiving responses.

Categories
Angular JavaScript TypeScript

Angular HTTP Client — Configuring Requests

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we’ll look at how to configure the Angular HTTP client to make requests that we want to make.

URL Query Strings

We can use the HttpParams class to add URL query strings to our HtttpRequest .

For example, we can write the following code to get a response from the Unsplash API:

app.module.ts :

import { BrowserModule } from "@angular/platform-browser";  
import { NgModule } from "@angular/core";  
import { HttpClientModule } from "@angular/common/http";  
import { AppComponent } from "./app.component";

@NgModule({  
  declarations: [AppComponent],  
  imports: [BrowserModule, HttpClientModule],  
  providers: [],  
  bootstrap: [AppComponent]  
})  
export class AppModule {}

app.component.ts :

import { Component } from "@angular/core";  
import { HttpClient, HttpParams } from "@angular/common/http";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css]  
})  
export class AppComponent {  
  constructor(private http: HttpClient) {} 

  ngOnInit() {  
    this.getPhotos();  
  } 

  getPhotos() {  
    const options = {  
      params: new HttpParams()  
        .set("page", "1")  
        .set(  
          "client_id",  
          "your_unsplash_api_key"  
        )  
        .set("query", "photo")  
    }; 

    this.http  
      .get("https://api.unsplash.com/search/photos", options)  
      .subscribe(res => console.log(res));  
  }  
}

In the code above, we created a new HttpParams object and call the set method on the instance to add a new query string parameters.

Then we make the GET request with the get method with the options object passed into it, which sets the query string.

Use fromString to Create HttpParams

We can also create the query for theHttpParams from a string.

To do this, we can write the following:

app.component.ts :

import { Component } from "@angular/core";  
import { HttpClient, HttpParams } from "@angular/common/http";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  constructor(private http: HttpClient) {} 

  ngOnInit() {  
    this.getPhotos();  
  } 

  getPhotos() {  
    const options = {  
      params: new HttpParams({  
        fromString:  
          "page=1&client_id=your_unsplash_api_key&query=photo"  
      })  
    }; 

    this.http  
      .get("https://api.unsplash.com/search/photos", options)  
      .subscribe(res => console.log(res));  
  }  
}

In the code above, we passed in the whole query string as the value of the fromString property.

Debouncing Requests

We can use the Rxjs debouceTime operator to delay the request by the amount of time that we specify.

For example, we can write:

app.component.ts :

import { Component } from "@angular/core";  
import { HttpClient, HttpParams } from "@angular/common/http";  
import { debounceTime, distinctUntilChanged } from "rxjs/operators";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  constructor(private http: HttpClient) {} 

  ngOnInit() {  
    this.getPhotos();  
  } 

  getPhotos() {  
    const options = {  
      params: new HttpParams({  
        fromString:  
          "page=1&client_id=your_unsplash_api_key&query=photo"  
      })  
    }; 

    this.http  
      .get("https://api.unsplash.com/search/photos", options)  
      .pipe(  
        debounceTime(500),  
        distinctUntilChanged()  
      )  
      .subscribe(res => console.log(res));  
  }  
}

In the code above, we use the pipe method to apply the debounceTime operator to delay the request by 500 milliseconds.

Then we emit the value from the get observable when it’s changed by using the distinctUntilChanged operator.

Listening to Progress Events

We can listen to the progress of the request by setting the reportProgress option to true .

For example, we can listen to progress events as follows:

app.component.ts :

import { Component } from "@angular/core";  
import { HttpClient, HttpParams, HttpRequest } from "@angular/common/http";  
import {  
  debounceTime,  
  distinctUntilChanged,  
  last,  
  tap,  
  map  
} from "rxjs/operators";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  constructor(private http: HttpClient) {} ngOnInit() {  
    this.getPhotos();  
  } 
 
  getPhotos() {  
    const options = {  
      params: new HttpParams({  
        fromString:  
          "page=1&client_id=your_unsplash_api_key&query=photo"  
      }),  
      reportProgress: true  
    }; 

    const req = new HttpRequest(  
      "GET",  
      "https://api.unsplash.com/search/photos",  
      {},  
      options  
    ); 

    this.http  
      .request(req)  
      .pipe(  
        map(event => console.log(event)),  
        tap(message => console.log(message)),  
        last()  
      )  
      .subscribe(res => console.log(res));  
  }  
}

In the code above, we call the request method to make the request.

The method takes an HttpRequest object, which takes the HTTP request method as the first argument, the second is the URL, the 3rd is the payload, the 4th is the options.

We add the reportProgress into the options object.

Then to get the progress, we use the tap operator to log the progress.

We get something like:

{type: 3, loaded: 65536, total: 72147}

where loaded is the number of bytes loaded. The total is the total number of bytes loaded.

Security: XSRF Protection

Cross-site request forgery (XSRF) is an attack where attackers trick an authenticated user into unknowingly run actions on a website.

HttpClient has built-in XSRF protection mechanisms to prevent these attacks.

It sets the X-XSRF-TOKEN in the request headers with the XSRF token.

A server-side app can verify that the XSRF token is correct so that attackers can’t make a request by masquerading as a legitimate user.

Configuring Custom XSRF Cookie/Header Names

We can change the XSRF cookie or header name. by adding the HttpClientXsrfModule.withOptions() to the imports array of our app’s NgModule .

For example, we can write:

app.module.ts :

import { BrowserModule } from "@angular/platform-browser";  
import { NgModule } from "@angular/core";  
import { HttpClientModule, HttpClientXsrfModule } from "@angular/common/http";  
import { AppComponent } from "./app.component";

@NgModule({  
  declarations: [AppComponent],  
  imports: [  
    BrowserModule,  
    HttpClientModule,  
    HttpClientXsrfModule.withOptions({  
      cookieName: "My-Xsrf-Cookie",  
      headerName: "My-Xsrf-Header"  
    })  
  ],  
  providers: [],  
  bootstrap: [AppComponent]  
})  
export class AppModule {}

In the code above, we changed the key of the XSRF cookie to My-Xsrf-Cookie and the key of the XSRF header to My-Xsrf-Header .

Conclusion

We can use the HttpParams object to set the query string of our requests.

To debounce requests, we can use the debounceTime operator to delay requests.

With Angular’s HTTP client, we can also listen to the progress of our request by setting the reportProgress option to true .

Angular’s HTTP client has XSRF protection built-in to secure the requests.

Categories
Angular JavaScript TypeScript

Using Angular’s Internationalization Features

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we’ll look at Angular’s internationalization features to write locale-sensitive apps.

Angular and i18n

Internationalization is the process of designing and prepare an app to be usable in different languages.

Localization is the process of translating an internationalized app into specific languages for particular locales.

Angular can internationalize the displaying of dates, numbers, percentages, and currencies in a local format.

It can also prepare text in component templates for translation, handling plural forms of words, and handle alternative text.

Setting Up the Locale of an App

The locale is an identifier that refers to a set of user preferences that are shared within a region of the world.

For example, fr-CA is Canadian French and fr is just French.

i18n Pipes

We can use Angular pipes for internationalization. The DatePipe , CurrencyPipe , DecimalPipe and PercentPipe all use locale data to format data based on the LOCALE_ID .

For example, we can set the locale and use the DatePipe to display Canadian French dates as follows:

app.module.ts :

import { BrowserModule } from "@angular/platform-browser";  
import { NgModule, LOCALE_ID } from "@angular/core";  
import { AppComponent } from "./app.component";  
import { registerLocaleData } from "@angular/common";  
import localeFr from "@angular/common/locales/fr";

@NgModule({  
  declarations: [AppComponent],  
  imports: [BrowserModule],  
  providers: [{ provide: LOCALE_ID, useValue: "fr-ca" }],  
  bootstrap: [AppComponent]  
})  
export class AppModule {}registerLocaleData(localeFr, "fr");

app.component.ts :

import { Component } from "@angular/core";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  date = new Date();  
}

app.component.html :

<div>{{date | date}}</div>

In the code above, we have the following in AppModule:

providers:\[{ provide: LOCALE_ID, useValue: "fr-ca" }],

to set the locale to Canadian French.

We also have:

registerLocaleData(localeFr, "fr");

to register or locale data.

We need both expressions whenever we want to use the internationalization features in our template.

Then we set the date in AppComponent and then reference it and pass it to the date pipe in our template.

Template Translations

We can add template translations with Angular.

To translate templates, we have to do 4 steps:

  1. Mark static text messages in ou component templates for translation
  2. Create a translation file by using the Angular CLI’s xi18n command to extract translations into a source file
  3. Edit the generated translation fil;e
  4. Merge the completed translation files into the app by using the Angular CLI’s build command with the --i18nFile option with the path of the translation file, --i18nFormat to specify the format of the translation file, and --i18nLocale with the locale ID

This replaces the original messages with the translated text and generates a new version of the app in the target language.

We mark the text with the i18n attribute to mark it for translation.

To do that, we write:

<h1 i18n>Hi</h1>

We can add a description and meaning as the value of the attribute:

<h1 i18n='Hi|Hello Message'>Hi</h1>

The text before the pipe is the meaning and the right text is the description.

We can specify a custom ID with @@ prefixed to our ID:

<h1 i18n="@@hi">Hi</h1>

We can also use it with a description:

<h1 i18n="Hello message@@hi">Hi</h1>

Translate Attributes

We prefix the attribute we want to translate with the i18n- prefix to mark it for translation.

For example, we can use the i18n-title to translate the title attribute. To do that, we write:

<img [src]="logo" i18n-title title="logo" />

Pluralization

Angular is also capable of pluralizing strings.

For example, we can do that as follows:

app.component.ts :

import { Component } from "@angular/core";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  minutes = 1;  
}

app.component.html :

<p i18n>  
  Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}}  
  minutes ago}}  
</p>

In the code above, we specified the minutes value in AppComponent .

Then in our template, we have the i18n attribute with:

=0 {just now} =1 {one minute ago} other {{{minutes}}  
  minutes ago}

to set the text for the what to display according to the value of minutes .

If it’s 0, we display ‘just now’. If it’s 1 we display ‘one minute ago’ and any other value we display {{{minutes}} minutes ago} .

Pluralization categories include (depending on the language):

  • =0 (or any other number)
  • zero
  • one
  • two
  • few
  • many
  • other

Select Among Alternative Text Messages

We can do something similar for other text messages.

For example, we can write:

app.component.ts :

import { Component } from "@angular/core";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  gender = "male";  
}

app.component.html :

<p i18n>  
  The author is {gender, select, male {male} female {female} other {other}}  
</p>

In the code above, we set the gender to 'male' , so we get the value 'male' displayed.

select selects the value of from the ones we indicated in the text that comes after it.

Then end result should display:

The author is male

Nesting Plural and Select ICU expressions

We can combine select and plural expressions together by nesting them.

For example, we can write:

app.component.ts :

import { Component } from "@angular/core";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent {  
  minutes = 3;  
  gender = "male";  
}

app.component.html :

<p i18n>  
  Updated: {minutes, plural, =0 {just now} =1 {one minute ago} other  
  {{{minutes}} minutes ago by {gender, select, male {male} female {female} other  
  {other}}}}  
</p>

to nest a select expression in a plural expression.

Then we get:

Updated: 3 minutes ago by male

displayed on the screen according to the values we set in AppComponent .

Conclusion

Angular has internationalization and localization features to make locale-sensitive apps.

The built-in pipes all support these features.

Also, it has libraries for making translations, pluralization, and selecting text according to values.

Categories
Angular JavaScript TypeScript

Angular Animations Transitions and Triggers

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we look at how to apply transitions and triggers when *ngIf is being run.

Predefined States and Wildcard Matching

We can use an asterisk to match any animation state. This is useful for defining transitions that apply regardless of the HTML element’s start or end state.

For example, we can use the asterisk as follows:

app.component.ts :

import { Component } from "@angular/core";  
import {  
  state,  
  style,  
  transition,  
  animate,  
  trigger  
} from "@angular/animations";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"],  
  animations: [  
    trigger("openClose", [  
      state(  
        "open",  
        style({  
          height: "250px",  
          opacity: 1,  
          backgroundColor: "pink"  
        })  
      ),  
      state(  
        "closed",  
        style({  
          height: "100px",  
          opacity: 0.5,  
          backgroundColor: "green"  
        })  
      ),  
      transition("* => closed", [animate("1s")]),  
      transition("* => open", [animate("0.5s")])  
    ])  
  ]  
})  
export class AppComponent {  
  isOpen = true;toggle() {  
    this.isOpen = !this.isOpen;  
  }  
}

app.component.html :

<button (click)="toggle()">Toggle</button>  
<div [@openClose]="isOpen ? 'open' : 'closed'">  
  {{ isOpen ? 'Open' : 'Closed' }}  
</div>

In the code above, we have:

transition("* => closed", [animate("1s")]),  
transition("* => open", [animate("0.5s")])

The code above indicates that we animate for a second when we transition from any state to the closed state.

When we transition from anything to the open state, we animate for half a second.

Using Wildcard State with Multiple Transition States

We can have more than 2 states in our transition. To use the asterisk to transition between multiple states as follows:

app.component.ts :

import { Component } from "@angular/core";  
import {  
  state,  
  style,  
  transition,  
  animate,  
  trigger  
} from "@angular/animations";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"],  
  animations: [  
    trigger("openClose", [  
      state(  
        "open",  
        style({  
          height: "250px",  
          opacity: 1,  
          backgroundColor: "pink"  
        })  
      ),  
      state(  
        "inProgress",  
        style({  
          height: "130px",  
          opacity: 0.75,  
          backgroundColor: "orange"  
        })  
      ),  
      state(  
        "closed",  
        style({  
          height: "100px",  
          opacity: 0.5,  
          backgroundColor: "green"  
        })  
      ),  
      transition("* => closed", [animate("1s")]),  
      transition("* => open", [animate("0.5s")]),  
      transition("* => inProgress", [animate("2.5s")]),  
      transition("inProgress => closed", [animate("1.5s")]),  
      transition("* => open", [animate("0.5s")])  
    ])  
  ]  
})  
export class AppComponent {  
  states = \["open", "closed", "inProgress"\];  
  index = 0;  
  state = "open"; changeState() {  
    this.index = (this.index + 1) % this.states.length;  
    this.state = this.states\[this.index\];  
  }  
}

app.component.html :

<button (click)="changeState()">Toggle</button>  
<div [@openClose]="state">  
  {{ state }}  
</div>

In the code above, we have multiple transition states and we used the wildcard to specify the animation length.

We cycled through the states with the changeState method.

We can use the void state to configure transitions for an element that’s entering or leaving a page.

It can be combined with the wildcard. It works as follows:

  • * => void — applies when the element leaves a view regardless of what state it was before it left
  • void => * — applies when the element enters a view regardless of what state it assumes when entering
  • The * state matches any state including void .

Animating Entering and Leaving a View

We can animate entering a leaving a view by styling the in state.

Then we animate the transition between the void and wildcard and vice versa to run animation when the element is being added removed and added respectively.

We can do that as follows:

app.component.ts :

import { Component } from "@angular/core";  
import {  
  state,  
  style,  
  transition,  
  animate,  
  trigger  
} from "@angular/animations";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"],  
  animations: [  
    trigger("flyInOut", [  
      state("in", style({ transform: "translateX(0)" })),  
      transition("void => *", [  
        style({ transform: "translateX(-100%)" }),  
        animate(100)  
      ]),  
      transition("* => void", [  
        animate(100, style({ transform: "translateX(100%)" }))  
      ])  
    ])  
  ]  
})  
export class AppComponent {}

app.component.html :

<button (click)="show = !show">Toggle</button>  
<div @flyInOut *ngIf="show">  
  foo  
</div>

In the code above, we have the flyInOut trigger which has the transition from void to * , which is applied when the div with the *ngIf is inserted.

The in state has the style for the div when it’s displayed.

Then the other transition is applied when the div is being removed.

In the template, we have div with the *ngIf with the word ‘foo’ inside and a Toggle button that toggles the div on and off.

Then when we click the Toggle button, we see the word ‘foo’ fly to the right before disappearing since we have:

transition("* => void", [  
  animate(100, style({ transform: "translateX(100%)" }))  
])

as the div is being removed with *ngIf .

:enter and :leave Aliases

:enter is short for void => * and :leave is shorthand for * => void .

So we can write:

transition ( ':enter', [ ... ] );  // alias for void => *  
transition ( ':leave', [ ... ] );  // alias for * => void

Conclusion

We can animate *ngIf transitions by adding the * => void and void => * transitions.

* stands for any state and void is the state when an element leaves the screen.

:enter and :leave are short for void => * and * => void respectively.

Categories
Angular JavaScript TypeScript

Introduction to Angular Animations

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we look at how to create animation effects in an Angular app.

Getting Started

To get started, we need to enable the animations module.

We do that by writing the following:

app.module.ts :

import { BrowserModule } from "@angular/platform-browser";  
import { NgModule } from "@angular/core";  
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";  
import { AppComponent } from "./app.component";

@NgModule({  
  declarations: [AppComponent],  
  imports: [BrowserModule, BrowserAnimationsModule],  
  providers: [],  
  bootstrap: [AppComponent]  
})  
export class AppModule {}

Then we import the animation functions into our component file as follows:

app.component.ts :

import {  
  trigger,  
  state,  
  style,  
  animate,  
  transition,  
  // ...  
} from '@angular/animations';

Finally, we add the animation metadata property to our component as follows:

@Component({  
  selector: 'app-root',  
  templateUrl: 'app.component.html',  
  styleUrls: ['app.component.css'],  
  animations: [  
    // animation triggers go here  
  ]  
})

Animation State and Styles

We call the state function to define different states to call at the end of each transition.

It takes a name and a style function. It takes an object with the CSS styles with the style attribute names in camelCase.

We then define transitions with the transition function, which takes the state transition string, and an array with the animate function call with the time string for animation.

All of the state and transition calls are wrapped in an array as the 2nd argument of the trigger function.

The first argument of the trigger function is the name of our trigger.

We write an app with all the parts to create a simple transition effect as follows:

app.module.ts :

import { BrowserModule } from "@angular/platform-browser";  
import { NgModule } from "@angular/core";  
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";  
import { AppComponent } from "./app.component";

@NgModule({  
  declarations: [AppComponent],  
  imports: [BrowserModule, BrowserAnimationsModule],  
  providers: [],  
  bootstrap: [AppComponent]  
})  
export class AppModule {}

app.component.ts :

import { Component } from "@angular/core";  
import {  
  state,  
  style,  
  transition,  
  animate,  
  trigger  
} from "@angular/animations";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"],  
  animations: [  
    trigger("openClose", [  
      state(  
        "open",  
        style({  
          height: "250px",  
          opacity: 1,  
          backgroundColor: "pink"  
        })  
      ),  
      state(  
        "closed",  
        style({  
          height: "100px",  
          opacity: 0.5,  
          backgroundColor: "green"  
        })  
      ),  
      transition("open => closed", [animate("1s")]),  
      transition("closed => open", [animate("0.5s")])  
    ])  
  ]  
})  
export class AppComponent {  
  isOpen = true;toggle() {  
    this.isOpen = !this.isOpen;  
  }  
}

app.component.html :

<button (click)="toggle()">Toggle</button>  
<div [@openClose]="isOpen ? 'open' : 'closed'">  
  {{ isOpen ? 'Open' : 'Closed' }}  
</div>

In the code above, we imported the BrowserAnimationModule in AppModule .

Then in the animations metadata array of the AppComponent , we add the animation trigger with the trigger function.

We then have the transition states which are generated with the state function.

The state function takes a style function call which returns the styles for each state of the transition.

The style function takes an object with the CSS style for each transition state.

In the component, we have the toggle function toggle the isOpen field when it’s called.

With the transition function calls, we specify the animation intervals with the animate function with the time string passed in.

Finally, in the template, we add the openClose trigger to the div. We display the style for the `open' state when isOpen is true and display the style for the 'closed' state otherwise.

If we have another trigger, we can change the name openClose to the other trigger name.

In the end, when we click the Toggle button, we see a yellow background with a height of 250px and a pink background with a height of 100px. The opacity also changed as specified.

Animations API Summary

The Angular animations API has the following function:

  • trigger — starts the animation and serves as the container for all animation function calls.
  • style — defines the CSS style for each stage of the animation
  • state — creates a named set of CSS styles that should be applied on a successful transition to a given state
  • animate — specifies the timing information of the animation
  • transition — defines the animation sequence between 2 named states
  • keyframes — allows a sequential change between styles within a specified time interval
  • group — specifies a group of animation steps to be run in parallel
  • query — use to find one or inner HTML elements within the current element
  • sequence — specifies a list of animation steps that are sequentially
  • stagger — staggers the starting time for animation for multiple elements
  • animatiom — produces a reuseable animation that can be invoked from elsewhere
  • useAnimation — activates a reuseable animation
  • animateChild — allows animations on child components to be run within the same timeframe as the parent

Conclusion

We can create animations with the built-in Angular BrowserAnimationsModule .

To create a simple transition effect, we just have to specify a trigger, then the states of the animation with the styles.

Then we have to specify what to do when we transition to different states.