This article shows how SQL localized data can be added to a database using Angular 2 forms which can then be displayed without restarting the application. The ASP.NET Core localization is implemented using Localization.SqlLocalizer. This NuGet package is used to save and retrieve the dynamic localized data. This makes it possible to add localized data at run-time.
Code: https://github.com/damienbod/Angular2LocalizationAspNetCore
Posts in this series
- Angular 2 Localization with an ASP.NET Core MVC Service
- Creating and requesting SQL localized data in ASP.NET Core
- Adding SQL localization data using an Angular 2 form and ASP.NET Core
The ASP.NET Core API provides an HTTP POST action method which allows the user to add a new ProductCreateEditDto object to the application. The view model adds both product data and also localization data to the SQLite database using Entity Framework Core.
[HttpPost] public IActionResult Post([FromBody]ProductCreateEditDto value) { _productCudProvider.AddProduct(value); return Created("http://localhost:5000/api/ShopAdmin/", value); }
The Angular 2 app uses the ProductService to send the HTTP POST request to the ShopAdmin service. The post methods sends the payload as a json object in the body of the request.
public CreateProduct = (product: ProductCreateEdit): Observable<ProductCreateEdit> => { let item: string = JSON.stringify(product); this.setHeaders(); return this._http.post(this.actionUrlShopAdmin, item, { headers: this.headers }).map((response: Response) => <ProductCreateEdit>response.json()) .catch(this.handleError); }
The client model is the same as the server side view model. The ProductCreateEdit class has an array of localized records.
import { LocalizationRecord } from './LocalizationRecord'; export class ProductCreateEdit { Id: number; Name: string; Description: string; ImagePath: string; PriceEUR: number; PriceCHF: number; LocalizationRecords: LocalizationRecord[]; } export class LocalizationRecord { Key: string; Text: string; LocalizationCulture: string; }
The shop-admin.component.html template contains the form which is used to enter the data and this is then sent to the server using the product service. The forms in Angular 2 have changed a lot compared to Angular 1 forms. The form uses the ngFormModel and the ngControl to define the Angular 2 form specifics. These control items need to be defined in the corresponding ts file.
<form [ngFormModel]="productForm" (ngSubmit)="Create(productForm.value)"> <div class="form-group" [ngClass]="{ 'has-error' : !name.valid && submitted }"> <label class="control-label" for="name">{{ 'ADD_PRODUCT_NAME' | translate:lang }}</label> <em *ngIf="!name.valid && submitted">Required</em> <input id="name" type="text" class="form-control" placeholder="name" ngControl="name" [(ngModel)]="Product.Name"> </div> <div class="form-group" [ngClass]="{ 'has-error' : !description.valid && submitted }"> <label class="control-label" for="description">{{ 'ADD_PRODUCT_DESCRIPTION' | translate:lang }}</label> <em *ngIf="!description.valid && submitted">Required</em> <input id="description" type="text" class="form-control" placeholder="description" ngControl="description" [(ngModel)]="Product.Description"> </div> <div class="form-group" [ngClass]="{ 'has-error' : !priceEUR.valid && submitted }"> <label class="control-label" for="priceEUR">{{ 'ADD_PRODUCT_PRICE_EUR' | translate:lang }}</label> <em *ngIf="!priceEUR.valid && submitted">Required</em> <input id="priceEUR" type="number" class="form-control" placeholder="priceEUR" ngControl="priceEUR" [(ngModel)]="Product.PriceEUR"> </div> <div class="form-group" [ngClass]="{ 'has-error' : !priceCHF.valid && submitted }"> <label class="control-label" for="priceCHF">{{ 'ADD_PRODUCT_PRICE_CHF' | translate:lang }}</label> <em *ngIf="!priceCHF.valid && submitted">Required</em> <input id="priceCHF" type="number" class="form-control" placeholder="priceCHF" ngControl="priceCHF" [(ngModel)]="Product.PriceCHF"> </div> <div class="form-group" [ngClass]="{ 'has-error' : !namede.valid && !namefr.valid && !nameit.valid && !nameen.valid && submitted }"> <label>{{ 'ADD_PRODUCT_LOCALIZED_NAME' | translate:lang }}</label> <div class="row"> <div class="col-md-3"><em>de</em></div> <div class="col-md-9"> <input class="form-control" type="text" [(ngModel)]="Name_de" ngControl="namede" #name="ngForm" /> </div> </div> <div class="row"> <div class="col-md-3"><em>fr</em></div> <div class="col-md-9"> <input class="form-control" type="text" [(ngModel)]="Name_fr" ngControl="namefr" #name="ngForm" /> </div> </div> <div class="row"> <div class="col-md-3"><em>it</em></div> <div class="col-md-9"> <input class="form-control" type="text" [(ngModel)]="Name_it" ngControl="nameit" #name="ngForm" /> </div> </div> <div class="row"> <div class="col-md-3"><em>en</em></div> <div class="col-md-9"> <input class="form-control" type="text" [(ngModel)]="Name_en" ngControl="nameen" #name="ngForm" /> </div> </div> </div> <div class="form-group" [ngClass]="{ 'has-error' : !descriptionde.valid && !descriptionfr.valid && !descriptionit.valid && !descriptionen.valid && submitted }"> <label>{{ 'ADD_PRODUCT_LOCALIZED_DESCRIPTION' | translate:lang }}</label> <div class="row"> <div class="col-md-3"><em>de</em></div> <div class="col-md-9"> <input class="form-control" type="text" [(ngModel)]="Description_de" ngControl="descriptionde" #name="ngForm" /> </div> </div> <div class="row"> <div class="col-md-3"><em>fr</em></div> <div class="col-md-9"> <input class="form-control" type="text" [(ngModel)]="Description_fr" ngControl="descriptionfr" #name="ngForm" /> </div> </div> <div class="row"> <div class="col-md-3"><em>it</em></div> <div class="col-md-9"> <input class="form-control" type="text" [(ngModel)]="Description_it" ngControl="descriptionit" #name="ngForm" /> </div> </div> <div class="row"> <div class="col-md-3"><em>en</em></div> <div class="col-md-9"> <input class="form-control" type="text" [(ngModel)]="Description_en" ngControl="descriptionen" #name="ngForm" /> </div> </div> </div> <div class="form-group"> <button type="submit" [disabled]="saving" class="btn btn-primary">{{ 'ADD_PRODUCT_CREATE_NEW_PRODUCT' | translate:lang }}</button> </div> </form>
The built in Angular 2 form components are imported from the ‘@angular/common’ library. The ControlGroup and the different Control items which are used in the HTML template need to be defined in the ts component file. The Control items are also added to the group in the builder function. The different Validators, or your own custom Validators can be added here. The Create method uses the Control items and the Product model to create the full product item and send the data to the Shop Admin Controller on the server. When successfully created, the user is redirected to the Shop component showing all products in the selected language.
import { Component, OnInit } from '@angular/core'; import { CORE_DIRECTIVES, NgForm, FORM_DIRECTIVES, FormBuilder, Control, ControlGroup, Validators } from '@angular/common'; import { Observable } from 'rxjs/Observable'; import { Http } from '@angular/http'; import { Product } from '../services/Product'; import { ProductCreateEdit } from '../services/ProductCreateEdit'; import { Locale, LocaleService, LocalizationService} from 'angular2localization/angular2localization'; import { ProductService } from '../services/ProductService'; import { TranslatePipe } from 'angular2localization/angular2localization'; import { Router} from '@angular/router'; @Component({ selector: 'shopadmincomponent', template: require('./shop-admin.component.html'), directives: [CORE_DIRECTIVES], pipes: [TranslatePipe] }) export class ShopAdminComponent extends Locale implements OnInit { public message: string; public Product: ProductCreateEdit; public Currency: string; public Name_de: string; public Name_fr: string; public Name_it: string; public Name_en: string; public Description_de: string; public Description_fr: string; public Description_it: string; public Description_en: string; productForm: ControlGroup; name: Control; description: Control; priceEUR: Control; priceCHF: Control; namede: Control; namefr: Control; nameit: Control; nameen: Control; descriptionde: Control; descriptionfr: Control; descriptionit: Control; descriptionen: Control; submitAttempt: boolean = false; saving: boolean = false; constructor( private router: Router, public _localeService: LocaleService, public localization: LocalizationService, private _productService: ProductService, private builder: FormBuilder) { super(null, localization); this.message = "shop-admin.component"; this._localeService.languageCodeChanged.subscribe(item => this.onLanguageCodeChangedDataRecieved(item)); this.buildForm(); } ngOnInit() { console.log("ngOnInit ShopAdminComponent"); // TODO Get product if Id exists this.initProduct(); this.Currency = this._localeService.getCurrentCurrency(); if (!(this.Currency === "CHF" || this.Currency === "EUR")) { this.Currency = "CHF"; } } buildForm(): void { this.name = new Control('', Validators.required); this.description = new Control('', Validators.required); this.priceEUR = new Control('', Validators.required); this.priceCHF = new Control('', Validators.required); this.namede = new Control('', Validators.required); this.namefr = new Control('', Validators.required); this.nameit = new Control('', Validators.required); this.nameen = new Control('', Validators.required); this.descriptionde = new Control('', Validators.required); this.descriptionfr = new Control('', Validators.required); this.descriptionit = new Control('', Validators.required); this.descriptionen = new Control('', Validators.required); this.productForm = this.builder.group({ name: ['', Validators.required], description: ['', Validators.required], priceEUR: ['', Validators.required], priceCHF: ['', Validators.required], namede: ['', Validators.required], namefr: ['', Validators.required], nameit: ['', Validators.required], nameen: ['', Validators.required], descriptionde: ['', Validators.required], descriptionfr: ['', Validators.required], descriptionit: ['', Validators.required], descriptionen: ['', Validators.required] }); } public Create() { this.submitAttempt = true; if (this.productForm.valid) { this.saving = true; this.Product.LocalizationRecords = []; this.Product.LocalizationRecords.push({ Key: this.Product.Name, LocalizationCulture: "de-CH", Text: this.Name_de }); this.Product.LocalizationRecords.push({ Key: this.Product.Name, LocalizationCulture: "fr-CH", Text: this.Name_fr }); this.Product.LocalizationRecords.push({ Key: this.Product.Name, LocalizationCulture: "it-CH", Text: this.Name_it }); this.Product.LocalizationRecords.push({ Key: this.Product.Name, LocalizationCulture: "en-US", Text: this.Name_en }); this.Product.LocalizationRecords.push({ Key: this.Product.Description, LocalizationCulture: "de-CH", Text: this.Description_de }); this.Product.LocalizationRecords.push({ Key: this.Product.Description, LocalizationCulture: "fr-CH", Text: this.Description_fr }); this.Product.LocalizationRecords.push({ Key: this.Product.Description, LocalizationCulture: "it-CH", Text: this.Description_it }); this.Product.LocalizationRecords.push({ Key: this.Product.Description, LocalizationCulture: "en-US", Text: this.Description_en }); this._productService.CreateProduct(this.Product) .subscribe(data => { this.saving = false; this.router.navigate(['/shop']); }, error => { this.saving = false; console.log(error) }, () => this.saving = false); } } private onLanguageCodeChangedDataRecieved(item) { console.log("onLanguageCodeChangedDataRecieved Shop Admin"); console.log(item + " : "+ this._localeService.getCurrentLanguage()); } private initProduct() { this.Product = new ProductCreateEdit(); } }
The form can then be used and the data is sent to the server.
And then displayed in the Shop component.
Notes
Angular 2 forms have a few validation issues which makes me uncomfortable using it.
Links
https://angular.io/docs/ts/latest/guide/forms.html
https://auth0.com/blog/2016/05/03/angular2-series-forms-and-custom-validation/
http://blog.thoughtram.io/angular/2016/03/14/custom-validators-in-angular-2.html
https://docs.asp.net/en/latest/fundamentals/localization.html
https://www.nuget.org/profiles/damienbod
https://github.com/robisim74/angular2localization
http://docs.asp.net/en/1.0.0-rc2/fundamentals/localization.html
