amp-web-push-widget button.amp-subscribe { display: inline-flex; align-items: center; border-radius: 5px; border: 0; box-sizing: border-box; margin: 0; padding: 10px 15px; cursor: pointer; outline: none; font-size: 15px; font-weight: 500; background: #4A90E2; margin-top: 7px; color: white; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.5); -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } /** * Jetpack related posts */ /** * The Gutenberg block */ .jp-related-posts-i2 { margin-top: 1.5rem; } .jp-related-posts-i2__list { --hgap: 1rem; display: flex; flex-wrap: wrap; column-gap: var(--hgap); row-gap: 2rem; margin: 0; padding: 0; list-style-type: none; } .jp-related-posts-i2__post { display: flex; flex-direction: column; /* Default: 2 items by row */ flex-basis: calc(( 100% - var(--hgap) ) / 2); } /* Quantity qeuries: see https://alistapart.com/article/quantity-queries-for-css/ */ .jp-related-posts-i2__post:nth-last-child(n+3):first-child, .jp-related-posts-i2__post:nth-last-child(n+3):first-child ~ * { /* From 3 total items on, 3 items by row */ flex-basis: calc(( 100% - var(--hgap) * 2 ) / 3); } .jp-related-posts-i2__post:nth-last-child(4):first-child, .jp-related-posts-i2__post:nth-last-child(4):first-child ~ * { /* Exception for 4 total items: 2 items by row */ flex-basis: calc(( 100% - var(--hgap) ) / 2); } .jp-related-posts-i2__post-link { display: flex; flex-direction: column; row-gap: 0.5rem; width: 100%; margin-bottom: 1rem; line-height: 1.2; } .jp-related-posts-i2__post-link:focus-visible { outline-offset: 2px; } .jp-related-posts-i2__post-img { order: -1; max-width: 100%; } .jp-related-posts-i2__post-defs { margin: 0; list-style-type: unset; } /* Hide, except from screen readers */ .jp-related-posts-i2__post-defs dt { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } .jp-related-posts-i2__post-defs dd { margin: 0; } /* List view */ .jp-relatedposts-i2[data-layout="list"] .jp-related-posts-i2__list { display: block; } .jp-relatedposts-i2[data-layout="list"] .jp-related-posts-i2__post { margin-bottom: 2rem; } /* Breakpoints */ @media only screen and (max-width: 640px) { .jp-related-posts-i2__list { display: block; } .jp-related-posts-i2__post { margin-bottom: 2rem; } } /* Container */ #jp-relatedposts { display: none; padding-top: 1em; margin: 1em 0; position: relative; clear: both; } .jp-relatedposts::after { content: ""; display: block; clear: both; } /* Headline above related posts section, labeled "Related" */ #jp-relatedposts h3.jp-relatedposts-headline { margin: 0 0 1em 0; display: inline-block; float: left; font-size: 9pt; font-weight: 700; font-family: inherit; } #jp-relatedposts h3.jp-relatedposts-headline em::before { content: ""; display: block; width: 100%; min-width: 30px; border-top: 1px solid rgba(0, 0, 0, 0.2); margin-bottom: 1em; } #jp-relatedposts h3.jp-relatedposts-headline em { font-style: normal; font-weight: 700; } /* Related posts items (wrapping items) */ #jp-relatedposts .jp-relatedposts-items { clear: left; } #jp-relatedposts .jp-relatedposts-items-visual { margin-right: -20px; } /* Related posts item */ #jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post { float: left; width: 33%; margin: 0 0 1em; /* Needs to be same as the main outer wrapper for Related Posts */ box-sizing: border-box; } #jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post { padding-right: 20px; filter: alpha(opacity=80); -moz-opacity: 0.8; opacity: 0.8; } #jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post:nth-child(3n+4), #jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post:nth-child(3n+4) { clear: both; } #jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post:hover .jp-relatedposts-post-title a { text-decoration: underline; } #jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post:hover { filter: alpha(opacity=100); -moz-opacity: 1; opacity: 1; } /* Related posts item content */ #jp-relatedposts .jp-relatedposts-items-visual h4.jp-relatedposts-post-title, #jp-relatedposts .jp-relatedposts-items p, #jp-relatedposts .jp-relatedposts-items time { font-size: 14px; line-height: 20px; margin: 0; } #jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post-nothumbs { position: relative; } #jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post-nothumbs a.jp-relatedposts-post-aoverlay { position: absolute; top: 0; bottom: 0; left: 0; right: 0; display: block; border-bottom: 0; } #jp-relatedposts .jp-relatedposts-items p, #jp-relatedposts .jp-relatedposts-items time { margin-bottom: 0; } #jp-relatedposts .jp-relatedposts-items-visual h4.jp-relatedposts-post-title { text-transform: none; margin: 0; font-family: inherit; display: block; max-width: 100%; } #jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post .jp-relatedposts-post-title a { font-size: inherit; font-weight: 400; text-decoration: none; filter: alpha(opacity=100); -moz-opacity: 1; opacity: 1; } #jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post .jp-relatedposts-post-title a:hover { text-decoration: underline; } #jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post img.jp-relatedposts-post-img, #jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post span { display: block; max-width: 90%; overflow: hidden; text-overflow: ellipsis; } #jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post img.jp-relatedposts-post-img, #jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post span { height: auto; max-width: 100%; } #jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post .jp-relatedposts-post-date, #jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post .jp-relatedposts-post-context { opacity: 0.6; } /* Hide the date by default, but leave the element there if * a theme wants to use css to make it visible. */ .jp-relatedposts-items .jp-relatedposts-post .jp-relatedposts-post-date { display: none; } /* Behavior when there are thumbnails in visual mode */ #jp-relatedposts .jp-relatedposts-items-visual div.jp-relatedposts-post-thumbs p.jp-relatedposts-post-excerpt { display: none; } /* Behavior when there are no thumbnails in visual mode */ #jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post-nothumbs p.jp-relatedposts-post-excerpt { overflow: hidden; } #jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post-nothumbs span { margin-bottom: 1em; } /* List Layout */ #jp-relatedposts .jp-relatedposts-list .jp-relatedposts-post { clear: both; width: 100%; } #jp-relatedposts .jp-relatedposts-list .jp-relatedposts-post img.jp-relatedposts-post-img { float: left; overflow: hidden; max-width: 33%; margin-right: 3%; } #jp-relatedposts .jp-relatedposts-list h4.jp-relatedposts-post-title { display: inline-block; max-width: 63%; } /* * Responsive */ @media only screen and (max-width: 640px) { #jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post { width: 50%; } #jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post:nth-child(3n) { clear: left; } #jp-relatedposts .jp-relatedposts-items-visual { margin-right: 20px; } } @media only screen and (max-width: 320px) { #jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post { width: 100%; clear: both; margin: 0 0 1em; } #jp-relatedposts .jp-relatedposts-list .jp-relatedposts-post img.jp-relatedposts-post-img, #jp-relatedposts .jp-relatedposts-list h4.jp-relatedposts-post-title { float: none; max-width: 100%; margin-right: 0; } } /* * Hide the related post section in the print view of a post */ @media print { .jp-relatedposts { display: none ; } } .wp-block-jetpack-rating-star span:not([aria-hidden="true"]) { display: none; } .amp-logo amp-img{width:190px} .amp-menu input{display:none;}.amp-menu li.menu-item-has-children ul{display:none;}.amp-menu li{position:relative;display:block;}.amp-menu > li a{display:block;}
X

Angular 2 Forms – Custom Validators

In this article we are not discussing how to develop forms in Angular 2, we are going to discuss how to write a custom validator for Angular 2 forms. Forms API in Angular 2 has some built-in validators already, here is an model driven form example with built-in required validator. Initial structure of the application: app/app.component.ts app/app.component.html app/main.ts index.html output in the browser: If we observer the output both the textboxes are red because both are required and there state is invalid, lets enter some data into them. Now both the textboxes are green, because we entered some data there state became valid. But there is one odd thing, we entered some text which not in correct email format. Still the textbox control state is valid, because we added only required validator it will not validate email format. Lets build custom validator to do that. In Angular 2 validator is a function which accepts Control as input parameter and returns StringMap<string, boolean>. Where the key is “error code” and the value is true if it fails. Lets define our custom validation function in CustomValidators class under app folder. app/CustomValidators.ts Above emailValidator function accepting input control as parameter, then comparing input value against email regular expression. If the value doesn’t match the regular expression then we are returning an error object. No we need to use this custom validator with our email control, but we already have required validator. To use multiple validators on single control we should use Validators.compose() function. app/app.component.ts Now even if enter value into email field it will not become green, until we enter email address in correct format. We can build any custom validator using similar approach. For intial setup and source code @ github

In this article we are not discussing how to develop forms in Angular 2, we are going to discuss how to write a custom validator for Angular 2 forms. Forms API in Angular 2 has some built-in validators already, here is an model driven form example with built-in required validator.

Initial structure of the application:

app/app.component.ts


import {Component} from '@angular/core';
import {ControlGroup, FormBuilder, Validators} from '@angular/common';
import {CustomValidators} from './CustomValidators';

@Component({
    selector: 'model-driven-forms-app',
    templateUrl:'/app/app.component.html',
    styles:[`
        .ng-valid {
          border: 1px solid #42A948;
        }
        .ng-invalid {
            border: 1px solid #a94442;
        }
    `]
})
export class AppComponent {

    loginForm: ControlGroup;

    constructor(fromBuilder: FormBuilder){

        this.loginForm = fromBuilder.group({
            email     : ['', Validators.required],
            password  : ['', Validators.required]
        });
    }

    onSubmit() {
        if(this.loginForm.valid) {
            console.log(this.loginForm);
        }
    }
}

app/app.component.html


<div class="container">
    <h3>Login Form</h3>
    <form [ngFormModel]="loginForm" (ngSubmit)="onSubmit()">
        <div class="form-group">
            <label for="email">Email</label>
            <input class="form-control" type="email"
                   id="email" [ngFormControl]="loginForm.controls['email']" />
        </div>
        <div class="form-group">
            <label for="password">Password</label>
            <input class="form-control" type="password"
                   id="password" [ngFormControl]="loginForm.controls['password']" />

        </div>
        <input class="btn btn-default" type="submit"
               value="Save" [disabled]="!loginForm.valid" />
    </form>
</div>

app/main.ts


import {bootstrap} from '@angular/platform-browser-dynamic';
import {AppComponent} from './app.component';

bootstrap(AppComponent);

index.html


<!DOCTYPE html>
<html>
<head>
    <title>Angular 2 Model Driven Forms</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">

    <!-- 1. Load libraries -->
    <!-- Polyfill(s) for older browsers -->
    <script src="node_modules/es6-shim/es6-shim.min.js"></script>

    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>

    <!-- 2. Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
        System.import('app').catch(function(err){ console.error(err); });
    </script>
</head>
<!-- 3. Display the application -->
<body>
    <model-driven-forms-app>
        Loading...
    </model-driven-forms-app>
</body>
</html>

output in the browser:

If we observer the output both the textboxes are red because both are required and there state is invalid, lets enter some data into them.

Now both the textboxes are green, because we entered some data there state became valid. But there is one odd thing, we entered some text which not in correct email format. Still the textbox control state is valid, because we added only required validator it will not validate email format. Lets build custom validator to do that.

In Angular 2 validator is a function which accepts Control as input parameter and returns StringMap<string, boolean>. Where the key is “error code” and the value is true if it fails.

Lets define our custom validation function in CustomValidators class under app folder.

app/CustomValidators.ts


import {Control} from '@angular/common';

export class CustomValidators {

    static emailValidator(control: Control) {
        if (!control.value.match(/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/)) {

            return { 'invalidEmailAddress': true };
        }
    }
}

Above emailValidator function accepting input control as parameter, then comparing input value against email regular expression. If the value doesn’t match the regular expression then we are returning an error object.

No we need to use this custom validator with our email control, but we already have required validator. To use multiple validators on single control we should use Validators.compose() function.

app/app.component.ts


import {Component} from '@angular/core';
import {ControlGroup, FormBuilder, Validators} from '@angular/common';
import {CustomValidators} from './CustomValidators';

@Component({
    selector: 'model-driven-forms-app',
    templateUrl:'/app/app.component.html',
    styles:[`
        .ng-valid {
          border: 1px solid #42A948;
        }
        .ng-invalid {
            border: 1px solid #a94442;
        }
    `]
})
export class AppComponent {

    loginForm: ControlGroup;

    constructor(fromBuilder: FormBuilder){

        this.loginForm = fromBuilder.group({
            email     : ['', Validators.compose([Validators.required, CustomValidators.emailValidator])],
            password  : ['', Validators.required]
        });
    }

    onSubmit() {
        if(this.loginForm.valid) {
            console.log(this.loginForm);
        }
    }
}

Now even if enter value into email field it will not become green, until we enter email address in correct format. We can build any custom validator using similar approach.

For intial setup and source code @ github

Categories: JAVASCRIPT
Shravan Kumar Kasagoni:

View Comments (2)

  • I get an error when trying to use the email validator: TypeError: Cannot read property 'match' of undefined