In this post, you will learn how to get started with Angular I18n using ngx-translate, the internationalization (i18n) library for Angular. We will cover the following topics:
setup new angular app
install required dependencies
add bootstrap as ui framework
create app with demo page and translation services
This will be the final result (click to show video). Source code for this post is on GitHub.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { HomePageComponent } from './pages/home/component';
import { DemoPageComponent } from './pages/demo/component';
@NgModule({
declarations: [AppComponent, HomePageComponent, DemoPageComponent],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
}),
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
// required for AOT compilation
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
return new TranslateHttpLoader(http);
}
How the app works
The translation is done with the ngx-translate component.
Translation works with different JSON files (for each language a separate file), containing the required translation for each text to be displayed. Each text is addressed with a name within the JSON file.
So, the base structure of each JSON file is the following:
Translation files
assets/i18n/de.json
{
"i18n-demo-header": "I18N Demo",
"header": "I18N Funktionalität in Angular"
}
assets/i18n/us.json
{
"i18n-demo-header": "I18N Example",
"header": "I18N Functionality in Angular"
}
These translations could be used in a html file by using the translate pipe:
function arrayPreviousLess(nums) {
let result = [];
let temp=[];
for(indx=0; indx < nums.length; indx++) {
curr = nums[indx];
// Build array with previous values
temp.push(curr)
// Find previous values less than current value
mins = temp.filter( (val) => val < curr)
// Return value at most right
max = (mins.length == 0) ? -1 : max = mins[mins.length-1];
result.push(max);
}
return result;
}
Day 17: Different symbols naive
function differentSymbolsNaive(str) {
m={};
n=0;
str.split("").sort().forEach( (c) => {
if (! (c in m)) n++;
m[c]=1;
});
return n
}
Day 16: Insert dashes
function insertDashes(arr) {
return arr.split(" ").map( part =>part.split("").join("-") ).join(" ");
}
function sumOddFibonacciNumbers(num) {
let fibPrev=1;
let fib=0;
let sum=0;
while (fibPrev < num) {
[ fibPrev, fib ] = [fib + fibPrev, fibPrev];
if (fib
sum += fib;
}
}
return sum;
}
The below code snippet we can view the result of using .map versus .switchMap
//user.service.ts
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from "rxjs";
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
@Injectable()
export class UserService {
constructor(private http: Http) { }
getUsers(): Observable<any> {
return this.http.get('http://jsonplaceholder.typicode.com/users')
//.map(v => v.json());
.switchMap(v => v.json());
}
}
//app.component.ts
import { Component } from '@angular/core';
import { UserService } from "./user.service";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app works!';
constructor(private userService: UserService) {
}
search(term: string) {
this.userService.getUsers()
.subscribe(v => console.log(v));
/*
//we can do this with .switchMap
this.userService.getUsers()
.subscribe(v => {if (v.email != "Sincere@april.biz") {
console.log(v.email);
}});
*/
}
}
Solving the multiple Async Pipe in Angular ≥ 2.0.0 with share operator
Remember to import import "rxjs/add/operator/share"; See reference here.
squareData$: Observable<string> = Observable.range(0, 10)
.map(x => x * x)
.do(x => console.log(`CalculationResult: ${x}`)
.toArray()
.map(squares => squares.join(", "))
.share(); // remove this line: console will log every result 3 times instead of 1
Managing Cold and Hot Observables using publish().refCount() which is similar to .share()
ngOnInit() {
// in angular 2 and above component.ts file add these this.coldObservable();
this.hotObservable();
}
/*
* cold observable is like a recast of video
* */
coldObservable() {
let incrementalObs = Observable.interval(1000).take(10).map(x => x + 1);
incrementalObs.subscribe(val => console.log('a: ' + val));
setTimeout(function() {
incrementalObs.subscribe(val => console.log(' b: ' + val));
}, 4500);
}
/*
* hot observable is like watching a live video
* */
hotObservable() {
let incrementalObs = Observable.interval(1000).take(10).map(x => x + 1).publish().refCount(); //can also use .share()
incrementalObs.subscribe(val => console.log('a: ' + val));
setTimeout(function() {
incrementalObs.subscribe(val => console.log(' b: ' + val));
}, 4500);
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
title = 'app works!';
obs = Observable.of(1, 2, 3, 4);
ngOnInit() {
this.howToHandleErrorV1();
this.howToHandleErrorV2();
this.howToUseRetry();
this.mergeObservableAndThrowError();
this.mergeObservableAndErrorResumeNext();
this.mergeObservableAndErrorCatch();
}
/*
* This uses Catch for V1. This introduces Closure. It is effectively the same as V2.
* */
howToHandleErrorV1() {
this.obs
.map(x => {
if ( x === 3 ) {
throw 'I hate threes'; // When it hitted error it actually unsubscribe itself at x === 3 of throw error
}
return x;
})
.catch(err => Observable.throw('Caught error here Observable.throw')) // continue go down the error path use Observable.throw
.catch(err => Observable.of('Caught error here Observable.of')) // catch just use Observable.of
.subscribe(
x => console.log(x),
err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
() => console.log('done completed')
);
}
/*
* There is a difference between V1 and V2. For V2 it is using onErrorResumeNext which
* */
howToHandleErrorV2() {
let good = Observable.of('Caught error here Observable.of');
this.obs
.map(x => {
if ( x === 3 ) {
throw 'I hate threes'; // When it hit error it actually unsubscribe itself at x === 3 of throw error
}
return x;
})
.onErrorResumeNext(good) // To catch just use Observable.of
.subscribe(
x => console.log(x),
err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
() => console.log('done completed')
);
}
/*
* For this we use see it retries three times then console.error(err);
* So retryWhen is for trying network connection websocket
* */
howToUseRetry() {
this.obs
.map(x => {
if ( x === 3 ) {
throw 'I hate threes'; // When it hitted error it actually unsubscribe itself at x === 3 of throw error
}
return x;
})
.retry(3) // retry three times
.retryWhen(err => err.delay(2000).take(3)) // similar but with 2 seconds delay and the error is not propagated.
.retryWhen(err => err.delay(2000).take(3).concat(Observable.throw('bad'))) // this it would throw an error.
.subscribe(
x => console.log(x),
err => console.error(err), // If not catch any where, the I hate threes errors will be propagated to here
() => console.log('done completed')
);
}
/*
* Using observable merge operator
* */
mergeObservableAndThrowError() {
let mergedObs = Observable.merge(
this.obs, //1, 2, 3, 4
Observable.throw('Stop Error'),
Observable.from(this.array), //0, 1, 2, 3, 4, 5
Observable.of(999) //999,
);
mergedObs.subscribe(
val => console.log(val), //this should show 1, 2, 3, 4, Stop Error
error => console.log(error),
() => console.log("completed")
);
}
/* Using observable onErrorResumeNext just like merge operator
* */
mergeObservableAndErrorResumeNext() {
let mergedObs = Observable.onErrorResumeNext(
this.obs, //1, 2, 3, 4
Observable.throw('Stop Error'),
Observable.from(this.array), //0, 1, 2, 3, 4, 5
Observable.of(999) //999,
);
mergedObs.subscribe(
val => console.log(val), //this should show 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 999
error => console.log(error),
() => console.log("completed")
);
}
/*
* Using observable merge operator and catch
* */
mergeObservableAndErrorCatch() {
let mergedObs = Observable.merge(
this.obs, //1, 2, 3, 4
Observable.throw('Stop Error'),
Observable.from(this.array), //0, 1, 2, 3, 4, 5
Observable.of(999) //999,
).catch(e => {
console.log(e);
return Observable.of('catch error here');
});
mergedObs.subscribe(
val => console.log(val), //this should show 1, 2, 3, 4, Stop Error, Catch Error Here
error => console.log(error),
() => console.log("completed")
);
}
}
map vs flatMap in RxJS
Transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable. See my GitHub repo.
obs = Observable.of(1, 2, 3, 4);
ngOnInit() {
this.usingMap();
this.usingMapToMakeInnerObservable();
this.usingMapAndMergeAll();
this.usingFlatMap();
}usingMap() {
this.obs
.map(x => x * 2) // transform the input by multiple of 2
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
usingMapToMakeInnerObservable() {
this.obs
.map(x => Observable.timer(500).map(() => x + 3)) // transform the input wrapping it with another observable and addition of 3
//.map(x => Observable.timer(500).map((x) => x + 3)) // !!! REMEMBER Not the same as the immediate above
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
// Map and Merge all is the same as just one FlatMap
usingMapAndMergeAll() {
this.obs
.map(x => Observable.timer(500).map(() => x + 3)) // transform the input wrapping it with another observable and addition of 3
.mergeAll()
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
// Flat map is the same as map then merge all
// transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable
usingFlatMap() {
this.obs
.flatMap(x => Observable.timer(500).map(() => x + 10)) // transform the input wrapping it with another observable and addition of 10
.subscribe(
x => console.log(x),
err => console.error(err),
() => console.log('done completed')
);
}
Transforming pure Javascript array vs. Observable from array
array = [0, 1, 2, 3, 4, 5];
ngOnInit() {
this.setArrayToObservableThenTransform();
}/*
* This keeps creating new array. It is good that it creates new array of arr for immutability.
* But it's bad because there is clean up and resource intensive for mobile
* */
transformArray() {
let result = this.array
.filter(( x, i, arr ) => {
console.log('filtering ' + x);
console.log('is the source array ' + (arr === this.array));
return x
})
.map(( x, i, arr ) => {
console.log('mapping ' + x);
console.log('is the source array ' + (arr === this.array));
return x + '!';
})
.reduce(( r, x, i, arr ) => {
console.log('reducing ' + x);
return r + x;
}, '--');
console.log(result);
}
/*
* This is more efficient for resource management because it linearly scans and discard when not right
* */
setArrayToObservableThenTransform() { let obsArray = Observable.from(this.array); // Use Observable.from() instead of Observable.of(). There is diff. obsArray
.filter(( x: any ) => {
console.log('filtering ' + x);
return x
})
.map(( x ) => {
console.log('mapping ' + x);
return x + '!';
})
.reduce(( r, x ) => {
console.log('reducing ' + x);
return r + x;
}, '--')
.subscribe(
x => console.log(x)
);
}
array = [0, 1, 2, 3, 4, 5];ngOnInit() {
this.reduceArray();
this.reduceObservableArray();
this.reduceObservableArray_Abstract2();
this.scanObservableArray();
}
/*
* This is the same as reduceObservableArray()
* */
reduceArray() {
let result = this.array.reduce(
(accumulator, currentValue) => accumulator + currentValue, 3
); // 3 is the init value.
console.log('reduceArray ' + result); // output 18 => 3 + (0 ... 5)
}
/*
* This is the same as reduceArray()
* But this waits for all the arrays to finish emitting before reducing them to one single number
* See the next method to understand better
* */
reduceObservableArray() {
let obsArray = Observable.from(this.array);
obsArray.reduce(
(accumulator, currentValue) => accumulator + currentValue, 3
).subscribe(
val => console.log('reduceObservableArray ' + val)
);
}
/*
* The exact same reduce function/method as of reduceObserableArray() above
* This proves that it waits for all 6 numbers to come in then reduce them
* */
reduceObservableArray_Abstract2() {
let obsArray = Observable.interval(1000).take(6); //emits 6 times of 0, 1, 2, 3, 4, 5
obsArray.reduce(
(accumulator, currentValue) => accumulator + currentValue, 3
).subscribe(
val => console.log('reduceObservableArray_Abstract2 ' + val)
);
}
/*
* This is the same as the above reduceObserableArray_Abstract2()
* except this is using scan instead of reduce
* */
scanObservableArray() {
let obsArray = Observable.interval(1000).take(6); //emits 6 times of 0, 1, 2, 3, 4, 5
obsArray.scan(
(accumulator, currentValue) => accumulator + currentValue, 3
).subscribe(
val => console.log('scanObservableArray() ' + val)
);
}
Create, next, and subscribe to Subject and BehaviorSubject
There is a Stack Overflow thread which discussed about the difference between Subject and BehaviorSubject. It’s worth understanding.
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
// create subject
// there is no need for initial value
subject = new Subject<boolean>();
// create behaviorSubject which require initial value
// true is an initial value. if there is a subscription
// after this, it would get true value immediately
behaviorSubject = new BehaviorSubject<boolean>(true);
ngOnInit() {
this.subject.next(false); /* Subject subscription wont get anything at this point before the subscribeSubject() */ this.subscribeSubject();
this.subscribeBehaviorSubject();
}
/*
* Push the next val into the behavior subject
* */
nextSubject(val: boolean) {
this.subject.next(val);
}
/*
* Any values push into the subject would not be can shown
* before this subscribeSubject() is called
* */
subscribeSubject() {
this.subject
//.take(1) //when we include .take(1) we will have a complete. Without this it will continue subscribing
.subscribe(
val => console.log(val),
err => console.error(err),
() => console.log('completed')
);
}
/*
* This is the proper way to return a subject as observable
* */
getSubject(): Observable<boolean> {
return this.subject.asObservable();
}
/*
* Push the next val into the behavior subject
* */
nextBehaviorSubject(val: boolean) {
this.behaviorSubject.next(val);
}
/*
* For angular Behavior subject for a data service as a angular service often initializes
* before component and behavior subject ensures that the component consuming the
* service receives the last updated data even if there are no new
* updates since the component's subscription to this data.
* */
subscribeBehaviorSubject() {
this.behaviorSubject
// .first()
.subscribe(
val => console.log(val),
err => console.error(err),
() => console.log('completed')
);
}
usingFinallyOperator() {
Observable
.interval(500)
.take(4)
.finally(() => console.log('End of the observable, Hello World'))
.subscribe(
val => console.log('count taker ' + val)
);
}
Stopping / Intercepting Observable
Imagine using Gmail where it allows you to undo email sent? We can produce similar experience with Observable
// subscription is created when an observable is being subscribed
subscription: Subscription;
// boolean variable for showing stop observable using takeWhile operator
isTrue: boolean = true;
/*
* basic interval can be used as delay too
* Imagine Gmail allows you to send and undo send within 4 seconds of sending
* Use Case: Perform an action 8 seconds later then intercept if user choose to undo the action
* */basicInterval() {
let undoInSeconds: number = 8;
this.subscription = Observable
.interval(1000)
.take(undoInSeconds)
.takeWhile(() => this.isTrue)
.subscribe(
(val: number) => {
console.log(`${val + 1} seconds... UNDO`);
( val === (undoInSeconds - 1) ) ? console.log('Email sent / Action performed') : null;
}
);
}
/*
* This is to stop observable from continuing performance
* Use Case: Stop observable from running like how Gmail could undo email being sent
* */stopObservableUsingUnsubscribe() {
if (!!this.subscription) {
this.subscription.unsubscribe();
console.log('subscription: Subscription is unsubscribed');
}
}
/*
* This is also to stop observable from continuing performance
* This method is more preferable than subscribing method then unsubscribe
* Use Case: Stop observable from running like how Gmail could undo email being sent
* */stopObservableUsingTakeWhile() {
this.isTrue = false;
}
Perform conditional Reactive Form validation
This is my approach to performing conditional validation when using Angular. We will minimally manipulate the Observable of RxJS in this example. Let’s try by creating or using app.component.ts.
1 . Create a form that has two form controls reason and otherReason.
/* * Refer to angular official guide at https://angular.io/guide/reactive-forms on how to create reactive form with form controls * */createForm() {
this.form = this.formBuilder.group({ reason: ['', Validators.required ],
otherReason: [''], });
}
2 . Create two methods addValidator and removeValidator.
/* * For conditional form validation use * */
private addValidator( control: AbstractControl, newValidator ){
let existingValidators = control.validator;
control.setValidators(Validators.compose([ existingValidators, newValidator ]));
control.updateValueAndValidity();
}
/* * For conditional form validation use * */
private removeValidator( control: AbstractControl ){
control.clearValidators();
control.updateValueAndValidity();
}
3 . Create third method called conditionalFormValidation.
The outcome should illustrate that if we select ‘Others’ option in the dropdown list of reason, it should make otherReason form control field as required.
Perform manual operations after reading from Firebase database
When using AngularFire2 with Angular + Firebase, in getting a list of data from Firebase, we will get one observable instance but within that one observable is an array of N size. We can manually filter the array inside that one observable instance using arr.filter. It is different from RxJS .filter operator. Of course we can also flatten what is inside an array using .flatMap() operator. However, we’re going use JavaScript array filtering, instead of non-observable filtering, right after getting an observable object.
We can also reverse an array using JavaScript array reverse function. See reference. On a side note, using negative timestamp to reverse Firebase display is also another option.
Besides those above, we can also use the response returned from AngularFire2 to perform “local filter/search”. This result can be valuable for autocomplete filtered list or searches. However, this approach below suffers severely in performance issue where at the magnitude of the size of the returned response from AngularFire2. E.g. if the list has N items. It has to iterate at least 1N. Perhaps an average of 2N.
$ node_modules/protractor/bin/webdriver-manager update
[17:17:13] I/update - chromedriver: file exists node_modules/protractor/node_modules/webdriver-manager/selenium/chromedriver_2.22mac32.zip
[17:17:13] I/update - chromedriver: unzipping chromedriver_2.22mac32.zip
[17:17:14] I/update - chromedriver: setting permissions to 0755 for node_modules/protractor/node_modules/webdriver-manager/selenium/chromedriver_2.22
[17:17:14] I/update - chromedriver: v2.22 up to date
[17:17:14] I/update - selenium standalone: file exists node_modules/protractor/node_modules/webdriver-manager/selenium/selenium-server-standalone-2.53.1.jar
[17:17:14] I/update - selenium standalone: v2.53.1 up to date
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept All”, you consent to the use of ALL the cookies. However, you may visit "Cookie Settings" to provide a controlled consent.
This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
Cookie
Duration
Description
cookielawinfo-checkbox-analytics
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional
11 months
The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
viewed_cookie_policy
11 months
The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.