{"id":26325,"date":"2018-06-29T09:00:00","date_gmt":"2018-06-29T09:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/premier_developer\/?p=26325"},"modified":"2019-02-21T11:12:10","modified_gmt":"2019-02-21T18:12:10","slug":"angular-how-to-share-server-side-validation","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/premier-developer\/angular-how-to-share-server-side-validation\/","title":{"rendered":"Angular How-to: Share Server-side Validation"},"content":{"rendered":"<p><strong>Laurie Atkinson, Senior Consultant, <\/strong>avoid duplication of field-level validation by dynamically applying server-side validation rules on Angular controls.<\/p>\n<hr \/>\n<p>Validation attributes are simple to apply to an Angular control within an HTML template, but that validation logic must be duplicated on the server. Instead, you can build an Angular validation service that parses server-generated rules and dynamically applies those rules using <a href=\"https:\/\/angular.io\/guide\/reactive-forms\">Reactive Forms<\/a>.<\/p>\n<h2>Settle on a schema that your API will provide<\/h2>\n<p>Your server-side API will need to return validation rules in some format that can be consumed by the Angular application. Here is an example that will be used in this post. It allows for multiple validation rules for each field and different validator types can include different attributes.<\/p>\n<p><code>{<\/code><\/p>\n<p><code>\u00a0 properties: [<\/code><\/p>\n<p><code>\u00a0 {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 fieldName: 'name',<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 rules: [{<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 type: 'requiredValidator',<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 requiredValidator: {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 errorMessage: 'Required'<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 },<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 type: 'maxLengthValidator',<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 maxLengthValidator: {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 maxLength: 30,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 errorMessage: 'Value should be no more than 30 characters'<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 }]<\/code><\/p>\n<p><code>\u00a0 },<\/code><\/p>\n<p><code>\u00a0 {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 fieldName: 'age',<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 rules: [{<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 type: 'numericRangeValidator',<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 numericRangeValidator: {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 minValue: 0,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 maxValue: 100,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 inclusive: true,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 errorMessage: 'Value must be between 0 and 100'<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 }]<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0 ]<\/code><\/p>\n<p><code>}<\/code><\/p>\n<h2>Create a TypeScript interface to match the API format<\/h2>\n<p>I prefer to use an interface for the data coming from the API, but a class could be used as well. For example:<\/p>\n<p><strong>validation.models.ts<\/strong><\/p>\n<p><code>import { AbstractControl, ValidatorFn } from '@angular\/forms';<\/code><\/p>\n<p><code>export type validationType = 'requiredValidator' |<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 'numericMinValueValidator' | 'numericMaxValueValidator' |<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 'maxLengthValidator' | 'numericRangeValidator' |<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 'dateMinValueValidator' | 'dateMaxValueValidator' | 'patternValidator';<\/code><\/p>\n<p><code>export interface IValidationRules {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 properties: Array&lt;{<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 fieldName: string,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rules: Array&lt;IValidator&gt;}&gt;;<\/code><\/p>\n<p><code>}<\/code><\/p>\n<p><code>export interface IValidator {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 type: validationType;<\/code><\/p>\n<p><code>}<\/code><\/p>\n<p><code>export interface IRequiredValidator extends IValidator {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 type: 'requiredValidator';<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 requiredValidator: {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 errorMessage: string;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>}<\/code><\/p>\n<p><code>export interface IMaxLengthValidator extends IValidator {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 type: 'maxLengthValidator';<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 maxLengthValidator: {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 maxLength: number,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 errorMessage: string<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>}<\/code><\/p>\n<p><code>export interface INumericRangeValidator extends IValidator {<\/code><\/p>\n<p><code>\u00a0\u00a0 \u00a0type: 'numericRangeValidator';<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 numericRangeValidator: {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 minValue: number,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 maxValue: number,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 inclusive: boolean,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 errorMessage: string<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>}<\/code><\/p>\n<p><code>export interface IPatternValidator extends IValidator {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 type: 'patternValidator';<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 patternValidator: {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 pattern: string,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 errorMessage: string<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>}<\/code><\/p>\n<p><code>export interface ICurrentControlValidators {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 control: AbstractControl;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 validators: Array&lt;ValidatorFn&gt;;<\/code><\/p>\n<p><code>}<\/code><\/p>\n<h2>Build the API validation service<\/h2>\n<p>Of course, the implementation of the API will depend on your server-side technology. I\u2019ll provide an example using ASP.NET WebAPI, which includes an option to add a <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/mvc\/controllers\/filters?view=aspnetcore-2.0\">filter attribute<\/a> to an action, a controller, or globally. This allows a request to an API to be intercepted and handled in an alternative way.<\/p>\n<p>So, if a method is decorated with a custom [Validate] filter, then custom code could be executed instead of the method\u2019s implementation. In our case, that custom code would be to generate the validation object and return that to the caller. In the following example, the implementation for the PUT inside the UpdateAddress() method would not execute if the Validate filter succeeds.<\/p>\n<p><code>[HttpPut]<\/code><\/p>\n<p><code>[Validate]<\/code><\/p>\n<p><code>public async Task&lt;IActionResult&gt; UpdateAddress([FromBody] address)<\/code><\/p>\n<p><code>{ \/\/ Implementation for PUT operation }<\/code><\/p>\n<p>The custom Validate filter must be able to detect that the caller is attempting to get the validation rules and use the datatype of the argument being passed into the API method to obtain the validation rules for each field in that object.<\/p>\n<p>If the client appends <em>getValidators<\/em> to the query string, that will trigger the execution of this filter.<\/p>\n<p><code>public class ValidateAttribute : ActionFilterAttribute<\/code><\/p>\n<p><code>{<\/code><\/p>\n<p><code>\u00a0 public override void OnActionExecuting(ActionExecutingContext actionContext)<\/code><\/p>\n<p><code>\u00a0 {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 var queryStrings = actionContext.HttpContext.Request.Query.ToDictionary(x =&gt; x.Key, x =&gt; x.Value);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 if (queryStrings.ContainsKey(\"getValidators\"))<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 emitClientValidation(actionContext);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 . . .<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 base.OnActionExecuting(actionContext);<\/code><\/p>\n<p><code>\u00a0 }<\/code><\/p>\n<p><code>\u00a0 private void emitClientValidation(ActionExecutingContext actionContext)<\/code><\/p>\n<p><code>\u00a0 {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 \/\/ Build a JSON object that matches the schema provided at the beginning of this<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 \/\/ article. Do this by looking at the validators on each property of the type<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 \/\/ of object the controller\u2019s method is expecting.<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 var sb = new StringBuilder();<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 using (var sw = new StringWriter(sb))<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 var wr = new JsonTextWriter(sw);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 wr.WriteStartObject();\u00a0\u00a0\u00a0\u00a0\u00a0<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0wr.WritePropertyName(\"properties\");<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 wr.WriteStartArray();<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Look at the argument for the called method.<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 var argument in actionContext.ActionDescriptor.Parameters[0];<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Recursively scan through the type of the object passed in<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ and read the model\u2019s validation attributes.<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 getValidationRules(argument.ParameterType, wr, null);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 wr.WriteEndArray();<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 wr.WriteEndObject();<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 wr.Close();<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Return OK with the validation object in the request body.<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 var obj = JsonConvert.DeserializeObject(sb.ToString());<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 actionContext.Result = new OkObjectResult(obj);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0 }<\/code><\/p>\n<p><code>\u00a0 private void getValidationRules(Type target, JsonTextWriter writer, string parent)<\/code><\/p>\n<p><code>\u00a0 {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 \/\/ Implementation of this method depends on your model validation library,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 \/\/ but it will involve Reflection to read the validation properties<\/code><\/p>\n<p><code>\u00a0 }<\/code><\/p>\n<p><code>}<\/code><\/p>\n<h2>Create Angular methods for each validator type<\/h2>\n<p>Each method should contain the TypeScript code to validate the control\u2019s value and return either an error message or null if valid. The following service shows how to implement 5 examples: required, minimum value, numeric range, maximum length, and pattern (i.e. regex). This is a starting point and could be expanded to include additional field validators as well as comparisons with other controls.<\/p>\n<p><strong>control-validators.service.ts<\/strong><\/p>\n<p><code>export class ControlValidators {<\/code><\/p>\n<p><code>\u00a0 static requiredValidator = (errorMessage: string) =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 return (control: AbstractControl) =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 const val: string = control.value;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 if (val == null || val.length === 0) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 requiredValidator: errorMessage)<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 return null;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>\u00a0 }<\/code><\/p>\n<p><code>\u00a0 static minValueValidator = (min: number, inclusive: boolean, errorMessage: string) =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 return (control: AbstractControl) =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 if (control.value === null) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return null;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 if (isNaN(control.value) || Number(control.value) &lt; min ||<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (!inclusive &amp;&amp; Number(control.value) === min)) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 minValueValidator: errorMessage<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 return null;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>\u00a0 }<\/code><\/p>\n<p><code>\u00a0 static numericRangeValidator = (range: {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0minValue: number,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 maxValue: number,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 inclusive?: boolean,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 errorMessage?: string<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 }) =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 return (control: AbstractControl) =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 if (control.value === null) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return null;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 const num = +control.value;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 if (isNaN(control.value) ||<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 !(num &lt;= range.maxValue &amp;&amp; num &gt;= range.minValue) ||<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (!range.inclusive &amp;&amp;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (num === range.maxValue || num === range.minValue))) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rangeValueValidator: range.errorMessage<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 return null;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>\u00a0 }<\/code><\/p>\n<p><code>\u00a0 static maxLengthValidator = (maxLength: number, errorMessage: string) =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 return (control: AbstractControl) =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 if (control.value &amp;&amp; control.value.length &gt; maxLength) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 maxLengthValidator: errorMessage<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 return null;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>\u00a0 }<\/code><\/p>\n<p><code>\u00a0 static patternValidator = (pattern: string, errorMessage: string) =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 return (control: AbstractControl) =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 if (control.value) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 const regex = new RegExp(`^${pattern}$`);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 const value = &lt;string&gt;control.value;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (!regex.test(value)) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 patternValidator: errorMessage<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 return null;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>\u00a0 }<\/code><\/p>\n<h2>Dynamically add validators to controls using Reactive Forms<\/h2>\n<p>Instead of using template-driven forms, use Reactive Forms to dynamically add controls.<\/p>\n<p>To learn more about Reactive Forms, refer to this <a href=\"https:\/\/angular.io\/guide\/reactive-forms\">documentation<\/a>.<\/p>\n<p>The component containing the form is responsible for using the <a href=\"https:\/\/angular.io\/api\/forms\/FormBuilder\">FormBuilder<\/a> class along with the <a href=\"https:\/\/angular.io\/api\/forms\/FormGroup\">FormGroup<\/a> class and the <a href=\"https:\/\/angular.io\/api\/forms\/FormGroup#addControl\">addControl()<\/a> method to create the form that includes all the <a href=\"https:\/\/angular.io\/api\/forms\/FormControl\">FormControl<\/a> objects.<\/p>\n<p>Then, the component will use the following service to apply the validation rules to those controls using the JSON returned by the API. <em>This requires that the names of the controls match the field names used in the JSON.<\/em><\/p>\n<p><strong>validation.service.ts<\/strong><\/p>\n<p><code>import { Injectable } from '@angular\/core';<\/code><\/p>\n<p><code>import { FormGroup } from '@angular\/forms';<\/code><\/p>\n<p><code>import { DataService } from '..\/..\/services\/data.service';<\/code><\/p>\n<p><code>import { INumberValidator, . . . } from '..\/models\/validation.models';<\/code><\/p>\n<p><code>import { ControlValidators } from '.\/control-validators.service';<\/code><\/p>\n<p><code>@Injectable()<\/code><\/p>\n<p><code>export class ValidationService {<\/code><\/p>\n<p><code>\u00a0 \/\/ FormGroup is one of the building blocks used in Reactive Forms.<\/code><\/p>\n<p><code>\u00a0 \/\/ DataService is a parent class from which all our http services inherit.<\/code><\/p>\n<p><code>\u00a0 \/\/ The dataService instance passed in should be a child class of DataService<\/code><\/p>\n<p><code>\u00a0 \/\/ and should include an implementation of a method that returns the API URL.<\/code><\/p>\n<p><code>\u00a0 applyValidationRules(formGroup: FormGroup, dataService: DataService) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 const endpoint = this.validationEndpoint(dataService);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 return new Promise&lt;Array&lt;ICurrentControlValidators&gt;&gt;((resolve) =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Call the PUT method (with the appended getValidators query string param)<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ and pass in a null form body since the API update will not be executed<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ put&lt;T&gt;(url: string, body: any): Promise&lt;T&gt;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 dataService.put&lt;IValidationRules&gt;(endpoint, null)<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .then(rules =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 resolve(this.addRulesToControls(formGroup, rules));<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 })<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .catch(() =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 resolve(null); \/\/ No validation rules<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 });<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 });<\/code><\/p>\n<p><code>\u00a0 }<\/code><\/p>\n<p><code>\u00a0 \/\/ Append getValidators=true to the request.<\/code><\/p>\n<p><code>\u00a0 \/\/ The API must be coded to recognize this parameter and rather than executing<\/code><\/p>\n<p><code>\u00a0 \/\/ the action, instead return a list of validation rules for each property.<\/code><\/p>\n<p><code>\u00a0 private validationEndpoint(dataService: DataService) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 \/\/ endpoint() should return the API URL for the request<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 let endpoint = dataService.endpoint();<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 endpoint += endpoint.indexOf('?') === -1 ? '?' : '&amp;';<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 endpoint += 'getValidators=true';<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 return endpoint;<\/code><\/p>\n<p><code>\u00a0 }<\/code><\/p>\n<p><code>\u00a0 \/\/ This method is called after successfully receiving the JSON containing the<\/code><\/p>\n<p><code>\u00a0 \/\/ rules for this form.<\/code><\/p>\n<p><code>\u00a0 private addRulesToControls(formGroup: FormGroup,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 validationRules: IValidationRules) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 const controlValidators = [];<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 if (validationRules &amp;&amp; validationRules.properties) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Go through each property returned by the API call<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ properties: [{<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/\u00a0\u00a0 fieldName: 'name',<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/\u00a0\u00a0 rules: [{ . . . }, { . . . }]<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ }, . . . ]<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 for (const prop of validationRules.properties) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\/\/ Use the methods in class ControlValidators described above to create<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ an array of all validators for a field.<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 const validatorArray = this.buildFieldValidators(prop.rules);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (validatorArray.length &gt; 0) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 const validators = this.applyValidatorToControl(formGroup,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 prop.fieldName, validatorArray, null);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (validators) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 controlValidators.push(validators);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 \/\/ return the list of validators, so that the component could append more.<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 return controlValidators;<\/code><\/p>\n<p><code>\u00a0 }<\/code><\/p>\n<p><code>\u00a0 \/\/ Given the list of rules from the JSON, return a list of the associated<\/code><\/p>\n<p><code>\u00a0 \/\/ TypeScript methods based on its type found in the ControlValidators class.<\/code><\/p>\n<p><code>\u00a0 private buildFieldValidators(rules: Array&lt;IValidator&gt;): any[] {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 const validatorArray: Array&lt;Function&gt; = [];<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 for (const rule of rules) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 switch (rule.type) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 case 'requiredValidator':<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 validatorArray.push(ControlValidators.requiredValidator(<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (&lt;IRequiredValidator&gt;rule).requiredValidator.errorMessage));<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 break;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 case 'numericRangeValidator':<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 validatorArray.push(ControlValidators.numericRangeValidator(<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (&lt;INumericRangeValidator&gt;rule).numericRangeValidator));<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 break;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 case 'maxLengthValidator':<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 validatorArray.push(ControlValidators.maxLengthValidator(<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (&lt;IMaxLengthValidator&gt;rule).maxLengthValidator.maxLength,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (&lt;IMaxLengthValidator&gt;rule).maxLengthValidator.errorMessage));<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 break;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 case 'patternValidator':<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 validatorArray.push(ControlValidators.patternValidator(<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (&lt;IPatternValidator&gt;rule).patternValidator.pattern,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0(&lt;IPatternValidator&gt;rule).patternValidator.errorMessage));<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 break;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 return validatorArray;<\/code><\/p>\n<p><code>\u00a0 }<\/code><\/p>\n<p><code>\u00a0 private applyValidatorToControl(formGroup: FormGroup, controlName: string,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 validatorArray: any[],<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0currentValidators: Array&lt;ICurrentControlValidators&gt;<\/code><\/p>\n<p><code>\u00a0 ): ICurrentControlValidators {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 \/\/ Find the control within the FormGroup by name.<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 \/\/ This is the reason that the name must match the field name in the JSON.<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 const control = formGroup &amp;&amp; formGroup.controls ?<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 formGroup.controls[controlName] : null;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 if (control) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 let validators = validatorArray;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ First see if any validators have been added to this control already.<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 if (currentValidators) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0const currentValidator = currentValidators.find(item =&gt; {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return item.control === control;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 });<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ If found, append this one to the list<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (currentValidator) {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 validators = currentValidator.validators.concat(validatorArray);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 control.<a href=\"https:\/\/angular.io\/api\/forms\/AbstractControl#setValidators\">setValidators<\/a>(validators);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 control.<a href=\"https:\/\/angular.io\/api\/forms\/AbstractControl#updateValueAndValidity\">updateValueAndValidity<\/a>(); \/\/ Trigger validation logic<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 return {<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 control: control,<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 validators: validators<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0\u00a0\u00a0 };<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 }<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 return null;<\/code><\/p>\n<p><code>\u00a0 }<\/code><\/p>\n<p><code>}<\/code><\/p>\n<p>This post is based on a successful production solution, but for clarity and space I have done quite a bit of \u201cpruning\u201d.\u00a0 I have attempted to include enough code that you have something that is readable and gives you a starting point. I hope it proves useful for your team.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Laurie Atkinson, Senior Consultant, avoid duplication of field-level validation by dynamically applying server-side validation rules on Angular controls. Validation attributes are simple to apply to an Angular control within an HTML template, but that validation logic must be duplicated on the server. Instead, you can build an Angular validation service that parses server-generated rules and [&hellip;]<\/p>\n","protected":false},"author":582,"featured_media":37840,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[51,61,366],"class_list":["post-26325","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-permierdev","tag-angular","tag-asp-net-core","tag-typescript"],"acf":[],"blog_post_summary":"<p>Laurie Atkinson, Senior Consultant, avoid duplication of field-level validation by dynamically applying server-side validation rules on Angular controls. Validation attributes are simple to apply to an Angular control within an HTML template, but that validation logic must be duplicated on the server. Instead, you can build an Angular validation service that parses server-generated rules and [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/26325","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/users\/582"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/comments?post=26325"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/26325\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media\/37840"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media?parent=26325"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/categories?post=26325"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/tags?post=26325"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}