Categories
Angular JavaScript TypeScript

Angular Animation Callbacks and Key Frames

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 animation callback and keyframes.

Animation Callbacks

The animation trigger emits callbacks when it starts and when it finishes.

For example, we can log the value of the event by writing the following code:

app.component.ts :

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

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"],  
  animations: [  
    trigger("openClose", [  
      state(  
        "true",  
        style({ height: "200px", opacity: 1, backgroundColor: "yellow" })  
      ),  
      state(  
        "false",  
        style({ height: "100px", opacity: 0.5, backgroundColor: "green" })  
      ),  
      transition("false <=> true", animate(500))  
    ])  
  ]  
})  
export class AppComponent {  
  onAnimationEvent(event: AnimationEvent) {  
    console.log(event);  
  }  
}

app.component.html :

<button (click)="show = !show">Toggle</button>  
<div  
  [@openClose]="show ? true: false"  
  (@openClose.start)="onAnimationEvent($event)"  
  (@openClose.done)="onAnimationEvent($event)"  
>  
  {{show ? 'foo' : ''}}  
</div>

In the code above, we have:

(@openClose.start)="onAnimationEvent($event)"  
(@openClose.done)="onAnimationEvent($event)"

to call the onAnimationEvent callback when the animation begins and ends respectively.

Then in our onAnimationEvent callback, we log the content of the event parameter.

It’s useful for debugging since it provides information about the states and elements of the animation.

Keyframes

We can add keyframes to our animation to create animations that are more complex than 2 stage animations.

For example, we can write the following:

app.component.ts :

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

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"],  
  animations: [  
    trigger("openClose", [  
      transition('true <=> false', [  
        animate('2s', keyframes([  
          style({ backgroundColor: 'blue' }),  
          style({ backgroundColor: 'red' }),  
          style({ backgroundColor: 'orange' })  
        ]))  
    ])  
  ]  
})  
export class AppComponent {  
  onAnimationEvent(event: AnimationEvent) {  
    console.log(event);  
  }  
}

app.component.html :

<button (click)="show = !show">Toggle</button>  
<div [@openClose]="show ? true: false">  
  {{show ? 'foo' : 'bar'}}  
</div>

In the code above, we add keyframes with different styles in AppComponent .

They’ll run in the order that they’re listed for the forward state transition and reverse for the reverse state transition.

Then when we click Toggle, we’ll see the color changes as the text changes.

Offset

Keyframes include an offset that defines the point in the animation where each style change occurs.

Offsets are relative measures from zero to one. They mark the beginning and end of the animation.

These are optional. Offsets are automatically assigned when they’re omitted.

For example, we can assign offsets as follows:

app.component.ts :

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

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"],  
  animations: [  
    trigger("openClose", [  
      transition('true <=> false', [  
        animate('2s', keyframes([  
          style({ backgroundColor: 'blue', offset: 0 }),  
          style({ backgroundColor: 'red', offset: 0.6 }),  
          style({ backgroundColor: 'orange', offset: 1 })  
        ]))  
    ])  
  ]  
})  
export class AppComponent {  
  onAnimationEvent(event: AnimationEvent) {  
    console.log(event);  
  }  
}

app.component.html :

<button (click)="show = !show">Toggle</button>  
<div [@openClose]="show ? true: false">  
  {{show ? 'foo' : 'bar'}}  
</div>

In the code above, we added offset properties to our style argument objects to change the timing of the color changes.

The color changes should shift slightly in timing compared to before.

Keyframes with a Pulsation

We can use keyframes to create a pulse effect by defining styles at a specific offset throughout the animation.

To add them, we can change the opacity of the keyframes as follows:

app.component.ts :

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

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"],  
  animations: [  
    trigger("openClose", [  
      transition('true <=> false', [  
        animate('1s', keyframes ( [  
          style({ opacity: 0.1, offset: 0.1 }),  
          style({ opacity: 0.6, offset: 0.2 }),  
          style({ opacity: 1,   offset: 0.5 }),  
          style({ opacity: 0.2, offset: 0.7 })  
        ]))  
    ])  
  ]  
})  
export class AppComponent {  
  onAnimationEvent(event: AnimationEvent) {  
    console.log(event);  
  }  
}

app.component.html :

<button (click)="show = !show">Toggle</button>  
<div [@openClose]="show ? true: false">  
  {{show ? 'foo' : 'bar'}}  
</div>

In the code above, we have the style argument objects that have the opacity and offset properties.

The opacity difference will create a pulsating effect.

The offset will change the timing of the opacity changes.

Then when we click Toggle, we should see the pulsating effect.

Automatic Property Calculation with Wildcards

We can set CSS style properties to a wildcard to do automatic calculations.

For example, we can use wildcards as follows:

app.component.ts :

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

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"],  
  animations: [  
    trigger("openClose", [  
      state("in", style({ height: "*" })),  
      transition("true => false", [  
        style({ height: "*", backgroundColor: "pink" }),  
        animate(250, style({ height: 0 }))  
      ]),  
      transition("false => true", [  
        style({ height: "*", backgroundColor: "yellow" }),  
        animate(250, style({ height: 0 }))  
      ])  
    ])  
  ]  
})  
export class AppComponent {  
  onAnimationEvent(event: AnimationEvent) {  
    console.log(event);  
  }  
}

app.component.html :

<button (click)="show = !show">Toggle</button>  
<div [@openClose]="show ? true: false">  
  {{show ? 'foo' : 'bar'}}  
</div>

In the code above, we set the height of the styles to a wildcard because we don’t want to set the height to a fixed height.

Then when we click Toggle, we see the color box grow and shrink as the animation runs.

Conclusion

We can add callbacks to our animation to debug our animations since we can log the values there.

To make more complex animations, we can use keyframes.

Offsets can be used to change the timing of the keyframes of the animation.

We can use wildcards to automatically set CSS style values.

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.