Angular
Click on the star icon to add the question to your favorites. Found an issue? Let us know.
Binding types can be grouped into three categories distinguished by the direction of data flow. They are listed as below,
- From the source-to-view
- From view-to-source
- View-to-source-to-view
The possible binding syntax can be tabularized as below,
Data direction Syntax Type From the source-to-view(One-way) 1. {{expression}} 2. [target]="expression" 3. bind-target="expression" Interpolation, Property, Attribute, Class, Style From view-to-source(One-way) 1. (target)="statement" 2. on-target="statement" Event View-to-source-to-view(Two-way) 1. [(target)]="expression" 2. bindon-target="expression" Two-way
A pure pipe is only called when Angular detects a change in the value or the parameters passed to a pipe. For example, any changes to a primitive input value (String, Number, Boolean, Symbol) or a changed object reference (Date, Array, Function, Object). An impure pipe is called for every change detection cycle no matter whether the value or parameters changes. i.e, An impure pipe is called often, as often as every keystroke or mouse-move.
Below are the list of differences between promise and observable,
Observable | Promise |
---|---|
Declarative: Computation does not start until subscription so that they can be run whenever you need the result | Execute immediately on creation |
Provide multiple values over time | Provide only one |
Subscribe method is used for error handling which makes centralized and predictable error handling | Push errors to the child promises |
Provides chaining and subscription to handle complex applications | Uses only .then() clause |
The RxJS library also provides below utility functions for creating and working with observables.
- Converting existing code for async operations into observables
- Iterating through the values in a stream
- Mapping values to different types
- Filtering streams
- Composing multiple streams
RxJS provides creation functions for the process of creating observables from things such as promises, events, timers and Ajax requests. Let us explain each of them with an example,
- Create an observable from a promise
import { from } from 'rxjs'; // from function const data = from(fetch('/api/endpoint')); //Created from Promise data.subscribe({ next(response) { console.log(response); }, error(err) { console.error('Error: ' + err); }, complete() { console.log('Completed'); } });
- Create an observable that creates an AJAX request
import { ajax } from 'rxjs/ajax'; // ajax function const apiData = ajax('/api/data'); // Created from AJAX request // Subscribe to create the request apiData.subscribe(res => console.log(res.status, res.response));
- Create an observable from a counter
import { interval } from 'rxjs'; // interval function const secondsCounter = interval(1000); // Created from Counter value secondsCounter.subscribe(n => console.log(`Counter value: ${n}`));
- Create an observable from an event
import { fromEvent } from 'rxjs'; const el = document.getElementById('custom-element'); const mouseMoves = fromEvent(el, 'mousemove'); const subscription = mouseMoves.subscribe((e: MouseEvent) => { console.log(`Coordnitaes of mouse pointer: ${e.clientX} * ${e.clientY}`); });
Since Angular elements are packaged as custom elements the browser support of angular elements is same as custom elements support. This feature is is currently supported natively in a number of browsers and pending for other browsers. | Browser | Angular Element Support | |---- | --------- | | Chrome | Natively supported| | Opera | Natively supported | | Safari| Natively supported | | Firefox | Natively supported from 63 version onwards. You need to enable dom.webcomponents.enabled and dom.webcomponents.customelements.enabled in older browsers | | Edge| Currently it is in progress|
Custom elements (or Web Components) are a Web Platform feature which extends HTML by allowing you to define a tag whose content is created and controlled by JavaScript code. The browser maintains a CustomElementRegistry
of defined custom elements, which maps an instantiable JavaScript class to an HTML tag. Currently this feature is supported by Chrome, Firefox, Opera, and Safari, and available in other browsers through polyfills.
No, custom elements bootstrap (or start) automatically when they are added to the DOM, and are automatically destroyed when removed from the DOM. Once a custom element is added to the DOM for any page, it looks and behaves like any other HTML element, and does not require any special knowledge of Angular.
You can use CLI command ng generate directive
to create the directive class file. It creates the source file(src/app/components/directivename.directive.ts
), the respective test file(.spec.ts) and declare the directive class file in root module.
Angular Universal is a server-side rendering module for Angular applications in various scenarios. This is a community driven project and available under @angular/platform-server package. Recently Angular Universal is integrated with Angular CLI.
Angular offers two ways to compile your application,
- Just-in-Time (JIT)
- Ahead-of-Time (AOT)
Just-in-Time (JIT) is a type of compilation that compiles your app in the browser at runtime. JIT compilation is the default when you run the ng build (build only) or ng serve (build and serve locally) CLI commands. i.e, the below commands used for JIT compilation,
ng build
ng serve
Ahead-of-Time (AOT) is a type of compilation that compiles your app at build time. For AOT compilation, include the --aot
option with the ng build or ng serve command as below,
ng build --aot
ng serve --aot
Note: The ng build command with the --prod meta-flag (ng build --prod
) compiles with AOT by default.
The Angular components and templates cannot be understood by the browser directly. Due to that Angular applications require a compilation process before they can run in a browser. For example, In AOT compilation, both Angular HTML and TypeScript code converted into efficient JavaScript code during the build phase before browser runs it.
Below are the list of AOT benefits,
- Faster rendering: The browser downloads a pre-compiled version of the application. So it can render the application immediately without compiling the app.
- Fewer asynchronous requests: It inlines external HTML templates and CSS style sheets within the application javascript which eliminates separate ajax requests.
- Smaller Angular framework download size: Doesn't require downloading the Angular compiler. Hence it dramatically reduces the application payload.
- Detect template errors earlier: Detects and reports template binding errors during the build step itself
- Better security: It compiles HTML templates and components into JavaScript. So there won't be any injection attacks.
Codelyzer provides set of tslint rules for static code analysis of Angular TypeScript projects. ou can run the static code analyzer over web apps, NativeScript, Ionic etc. Angular CLI has support for this and it can be use as below,
ng new codelyzer
ng lint
Angular's animation system is built on CSS functionality in order to animate any property that the browser considers animatable. These properties includes positions, sizes, transforms, colors, borders etc. The Angular modules for animations are @angular/animations and @angular/platform-browser and these dependencies are automatically added to your project when you create a project using Angular CLI.
A service worker is a script that runs in the web browser and manages caching for an application. Starting from 5.0.0 version, Angular ships with a service worker implementation. Angular service worker is designed to optimize the end user experience of using an application over a slow or unreliable network connection, while also minimizing the risks of serving outdated content.
Below are the list of design goals of Angular's service workers,
- It caches an application just like installing a native application
- A running application continues to run with the same version of all files without any incompatible files
- When you refresh the application, it loads the latest fully cached version
- When changes are published then it immediately updates in the background
- Service workers saves the bandwidth by downloading the resources only when they changed.
A DI token is a lookup token associated with a dependency provider in dependency injection system. The injector maintains an internal token-provider map that it references when asked for a dependency and the DI token is the key to the map. Let's take example of DI Token usage,
const BASE_URL = new InjectionToken<string>('BaseUrl');
const injector =
Injector.create({providers: [{provide: BASE_URL, useValue: 'http://some-domain.com'}]});
const url = injector.get(BASE_URL);
A domain-specific language (DSL) is a computer language specialized to a particular application domain. Angular has its own Domain Specific Language (DSL) which allows us to write Angular specific html-like syntax on top of normal html. It has its own compiler that compiles this syntax to html that the browser can understand. This DSL is defined in NgModules such as animations, forms, and routing and navigation. Basically you will see 3 main syntax in Angular DSL.
()
: Used for Output and DOM events.[]
: Used for Input and specific DOM element attributes.*
: Structural directives(*ngFor or *ngIf) will affect/change the DOM structure.
If multiple modules imports the same module then angular evaluates it only once (When it encounters the module first time). It follows this condition even the module appears at any level in a hierarchy of imported NgModules.
You can use @ViewChild
directive to access elements in the view directly. Let's take input element with a reference,
<input #uname>
and define view child directive and access it in ngAfterViewInit lifecycle hook
@ViewChild('uname') input;
ngAfterViewInit() {
console.log(this.input.nativeElement.value);
}
In Angular7, you can subscribe to router to detect the changes. The subscription for router events would be as below,
this.router.events.subscribe((event: Event) => {})
Let's take a simple component to detect router changes
import { Component } from '@angular/core';
import { Router, Event, NavigationStart, NavigationEnd, NavigationError } from '@angular/router';
@Component({
selector: 'app-root',
template: `<router-outlet></router-outlet>`
})
export class AppComponent {
constructor(private router: Router) {
this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationStart) {
// Show loading indicator and perform an action
}
if (event instanceof NavigationEnd) {
// Hide loading indicator and perform an action
}
if (event instanceof NavigationError) {
// Hide loading indicator and perform an action
console.log(event.error); // It logs an error for debugging
}
});
}
}
Yes, Angular 8 supports dynamic imports in router configuration. i.e, You can use the import statement for lazy loading the module using loadChildren
method and it will be understood by the IDEs(VSCode and WebStorm), webpack, etc.
Previously, you have been written as below to lazily load the feature module. By mistake, if you have typo in the module name it still accepts the string and throws an error during build time.
{path: ‘user’, loadChildren: ‘./users/user.module#UserModulee’},
This problem is resolved by using dynamic imports and IDEs are able to find it during compile time itself.
{path: ‘user’, loadChildren: () => import(‘./users/user.module’).then(m => m.UserModule)};
Lazy loading is one of the most useful concepts of Angular Routing. It helps us to download the web pages in chunks instead of downloading everything in a big bundle. It is used for lazy loading by asynchronously loading the feature module for routing whenever required using the property loadChildren
. Let's load both Customer
and Order
feature modules lazily as below,
const routes: Routes = [
{
path: 'customers',
loadChildren: () => import('./customers/customers.module').then(module => module.CustomersModule)
},
{
path: 'orders',
loadChildren: () => import('./orders/orders.module').then(module => module.OrdersModule)
},
{
path: '',
redirectTo: '',
pathMatch: 'full'
}
];
Angular 8.0 release introduces Workspace APIs to make it easier for developers to read and modify the angular.json file instead of manually modifying it. Currently, the only supported storage3 format is the JSON-based format used by the Angular CLI. You can enable or add optimization option for build target as below,
import { NodeJsSyncHost } from '@angular-devkit/core/node';
import { workspaces } from '@angular-devkit/core';
async function addBuildTargetOption() {
const host = workspaces.createWorkspaceHost(new NodeJsSyncHost());
const workspace = await workspaces.readWorkspace('path/to/workspace/directory/', host);
const project = workspace.projects.get('my-app');
if (!project) {
throw new Error('my-app does not exist');
}
const buildTarget = project.targets.get('build');
if (!buildTarget) {
throw new Error('build target does not exist');
}
buildTarget.options.optimization = true;
await workspaces.writeWorkspace(workspace, host);
}
addBuildTargetOption();
The Angular upgrade is quite easier using Angular CLI ng update
command as mentioned below. For example, if you upgrade from Angular 7 to 8 then your lazy loaded route imports will be migrated to the new import syntax automatically.
$ ng update @angular/cli @angular/core
NgUpgrade is a library put together by the Angular team, which you can use in your applications to mix and match AngularJS and Angular components and bridge the AngularJS and Angular dependency injection systems.
Angular CLI downloads and install everything needed with the Jasmine Test framework. You just need to run ng test
to see the test results. By default this command builds the app in watch mode, and launches the Karma test runner
. The output of test results would be as below,
10% building modules 1/1 modules 0 active
...INFO [karma]: Karma v1.7.1 server started at http://0.0.0.0:9876/
...INFO [launcher]: Launching browser Chrome ...
...INFO [launcher]: Starting browser Chrome
...INFO [Chrome ...]: Connected on socket ...
Chrome ...: Executed 3 of 3 SUCCESS (0.135 secs / 0.205 secs)
Note: A chrome browser also opens and displays the test output in the "Jasmine HTML Reporter".
The Angular CLI provides support for polyfills officially. When you create a new project with the ng new command, a src/polyfills.ts
configuration file is created as part of your project folder. This file includes the mandatory and many of the optional polyfills as JavaScript import statements. Let's categorize the polyfills,
- Mandatory polyfills: These are installed automatically when you create your project with ng new command and the respective import statements enabled in 'src/polyfills.ts' file.
- Optional polyfills: You need to install its npm package and then create import statement in 'src/polyfills.ts' file.
For example, first you need to install below npm package for adding web animations (optional) polyfill.
and create import statement in polyfill file.npm install --save web-animations-js
import 'web-animations-js';
You can inject either ApplicationRef or NgZone, or ChangeDetectorRef into your component and apply below specific methods to trigger change detection in Angular. i.e, There are 3 possible ways,
- ApplicationRef.tick(): Invoke this method to explicitly process change detection and its side-effects. It check the full component tree.
- NgZone.run(callback): It evaluate the callback function inside the Angular zone.
- ChangeDetectorRef.detectChanges(): It detects only the components and it's children.
Angular supports most recent browsers which includes both desktop and mobile browsers. | Browser | Version | |---- | --------- | | Chrome | latest | | Firefox | latest | | Edge | 2 most recent major versions | | IE | 11, 10, 9 (Compatibility mode is not supported) | | Safari | 2 most recent major versions | | IE Mobile | 11 | | iOS | 2 most recent major versions | | Android | 7.0, 6.0, 5.0, 5.1, 4.4 |
The innerHtml is a property of HTML-Elements, which allows you to set it's html-content programmatically. Let's display the below html code snippet in a <div>
tag as below using innerHTML binding,
<div [innerHTML]="htmlSnippet"></div>
and define the htmlSnippet property from any component
export class myComponent {
htmlSnippet: string = '<b>Hello World</b>, Angular';
}
Unfortunately this property could cause Cross Site Scripting (XSS) security bugs when improperly handled.
Http Interceptors are part of @angular/common/http, which inspect and transform HTTP requests from your application to the server and vice-versa on HTTP responses. These interceptors can perform a variety of implicit tasks, from authentication to logging. The syntax of HttpInterceptor interface looks like as below,
interface HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
}
You can use interceptors by declaring a service class that implements the intercept() method of the HttpInterceptor interface.
@Injectable()
export class MyInterceptor implements HttpInterceptor {
constructor() {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
...
}
}
After that you can use it in your module,
@NgModule({
...
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MyInterceptor,
multi: true
}
]
...
})
export class AppModule {}
The HTTP Interceptors can be used for different variety of tasks,
- Authentication
- Logging
- Caching
- Fake backend
- URL transformation
- Modifying headers
Yes, Angular supports multiple interceptors at a time. You could define multiple interceptors in providers property:
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: MyFirstInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: MySecondInterceptor, multi: true }
],
The interceptors will be called in the order in which they were provided. i.e, MyFirstInterceptor will be called first in the above interceptors configuration.
You can use same instance of HttpInterceptors
for the entire app by importing the HttpClientModule
only in your AppModule, and add the interceptors to the root application injector.
For example, let's define a class that is injectable in root application.
@Injectable()
export class MyInterceptor implements HttpInterceptor {
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).do(event => {
if (eventt instanceof HttpResponse) {
// Code goes here
}
});
}
}
After that import HttpClientModule in AppModule
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: MyInterceptor, multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule {}
An Angular library is an Angular project that differs from an app in that it cannot run on its own. It must be imported and used in an app. For example, you can import or add service worker
library to an Angular application which turns an application into a Progressive Web App (PWA).
Note: You can create own third party library and publish it as npm package to be used in an Application.
You can control any DOM element via ElementRef by injecting it into your component's constructor. i.e, The component should have constructor with ElementRef parameter,
constructor(myElement: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow';
}
The hidden property is used to show or hide the associated DOM element, based on an expression. It can be compared close to ng-show
directive in AngularJS. Let's say you want to show user name based on the availability of user using hidden
property.
<div [hidden]="!user.name">
My name is: {{user.name}}
</div>
The main difference is that *ngIf will remove the element from the DOM, while [hidden] actually plays with the CSS style by setting display:none
. Generally it is expensive to add and remove stuff from the DOM for frequent actions.
The slice pipe is used to create a new Array or String containing a subset (slice) of the elements. The syntax looks like as below,
{{ value_expression | slice : start [ : end ] }}
For example, you can provide 'hello' list based on a greeting array,
@Component({
selector: 'list-pipe',
template: `<ul>
<li *ngFor="let i of greeting | slice:0:5">{{i}}</li>
</ul>`
})
export class PipeListComponent {
greeting: string[] = ['h', 'e', 'l', 'l', 'o', 'm','o', 'r', 'n', 'i', 'n', 'g'];
}
The index property of the NgFor directive is used to return the zero-based index of the item in each iteration. You can capture the index in a template input variable and use it in the template. For example, you can capture the index in a variable named indexVar and displays it with the todo's name using ngFor directive as below.
<div *ngFor="let todo of todos; let i=index">{{i + 1}} - {{todo.name}}</div>
The main purpose of using *ngFor with trackBy option is performance optimization. Normally if you use NgFor with large data sets, a small change to one item by removing or adding an item, can trigger a cascade of DOM manipulations. In this case, Angular sees only a fresh list of new object references and to replace the old DOM elements with all new DOM elements. You can help Angular to track which items added or removed by providing a trackBy
function which takes the index and the current item as arguments and needs to return the unique identifier for this item.
For example, lets set trackBy to the trackByTodos() method
<div *ngFor="let todo of todos; trackBy: trackByTodos">
({{todo.id}}) {{todo.name}}
</div>
and define the trackByTodos method,
trackByTodos(index: number, item: Todo): number { return todo.id; }
NgSwitch directive is similar to JavaScript switch statement which displays one element from among several possible elements, based on a switch condition. In this case only the selected element placed into the DOM. It has been used along with NgSwitch
, NgSwitchCase
and NgSwitchDefault
directives.
For example, let's display the browser details based on selected browser using ngSwitch directive.
<div [ngSwitch]="currentBrowser.name">
<chrome-browser *ngSwitchCase="'chrome'" [item]="currentBrowser"></chrome-browser>
<firefox-browser *ngSwitchCase="'firefox'" [item]="currentBrowser"></firefox-browser>
<opera-browser *ngSwitchCase="'opera'" [item]="currentBrowser"></opera-browser>
<safari-browser *ngSwitchCase="'safari'" [item]="currentBrowser"></safari-browser>
<ie-browser *ngSwitchDefault [item]="currentItem"></ie-browser>
</div>
Yes, it is possible to do aliasing for inputs and outputs in two ways.
- Aliasing in metadata: The inputs and outputs in the metadata aliased using a colon-delimited (:) string with the directive property name on the left and the public alias on the right. i.e. It will be in the format of propertyName:alias.
inputs: ['input1: buyItem'], outputs: ['outputEvent1: completedEvent']
- Aliasing with @Input()/@Output() decorator: The alias can be specified for the property name by passing the alias name to the @Input()/@Output() decorator.i.e. It will be in the form of @Input(alias) or @Output(alias).
@Input('buyItem') input1: string; @Output('completedEvent') outputEvent1 = new EventEmitter<string>();
The safe navigation operator(?)(or known as Elvis Operator) is used to guard against null
and undefined
values in property paths when you are not aware whether a path exists or not. i.e. It returns value of the object path if it exists, else it returns the null value.
For example, you can access nested properties of a user profile easily without null reference errors as below,
<p>The user firstName is: {{user?.fullName.firstName}}</p>
Using this safe navigation operator, Angular framework stops evaluating the expression when it hits the first null value and renders the view without any errors.
The Angular template expression language supports three special template expression operators.
- Pipe operator
- Safe navigation operator
- Non-null assertion operator
An entry component is any component that Angular loads imperatively(i.e, not referencing it in the template) by type. Due to this behavior, they can’t be found by the Angular compiler during compilation. These components created dynamically with ComponentFactoryResolver
.
Basically, there are two main kinds of entry components which are following -
- The bootstrapped root component
- A component you specify in a route
What is a bootstrapped component?
A bootstrapped component is an entry component that Angular loads into the DOM during the bootstrap process or application launch time. Generally, this bootstrapped or root component is named asAppComponent
in your root module usingbootstrap
property as below.@NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, FormsModule, HttpClientModule, AppRoutingModule ], providers: [], bootstrap: [AppComponent] // bootstrapped entry component need to be declared here })
How do you manually bootstrap an application?
You can usengDoBootstrap
hook for a manual bootstrapping of the application instead of using bootstrap array in@NgModule
annotation. This hook is part ofDoBootstap
interface.
The module needs to be implement the above interface to use the hook for bootstrapping.interface DoBootstrap { ngDoBootstrap(appRef: ApplicationRef): void }
class AppModule implements DoBootstrap { ngDoBootstrap(appRef: ApplicationRef) { appRef.bootstrap(AppComponent); // bootstrapped entry component need to be passed } }
A bootstrapped component is an entry component that Angular loads into the DOM during the bootstrap process or application launch time. Generally, this bootstrapped or root component is named as AppComponent
in your root module using bootstrap
property as below.
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent] // bootstrapped entry component need to be declared here
})
How do you manually bootstrap an application?
You can usengDoBootstrap
hook for a manual bootstrapping of the application instead of using bootstrap array in@NgModule
annotation. This hook is part ofDoBootstap
interface.
The module needs to be implement the above interface to use the hook for bootstrapping.interface DoBootstrap { ngDoBootstrap(appRef: ApplicationRef): void }
class AppModule implements DoBootstrap { ngDoBootstrap(appRef: ApplicationRef) { appRef.bootstrap(AppComponent); // bootstrapped entry component need to be passed } }
The components referenced in router configuration are called as routed entry components. This routed entry component defined in a route definition as below,
const routes: Routes = [
{
path: '',
component: TodoListComponent // router entry component
}
];
Since router definition requires you to add the component in two places (router and entryComponents), these components are always entry components.
Note: The compilers are smart enough to recognize a router definition and automatically add the router component into entryComponents
.
A provider is an instruction to the Dependency Injection system on how to obtain a value for a dependency(aka services created). The service can be provided using Angular CLI as below,
ng generate service my-service
The created service by CLI would be as below,
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root', //Angular provide the service in root injector
})
export class MyService {
}
Angular provides a service called NgZone which creates a zone named angular
to automatically trigger change detection when the following conditions are satisfied.
- When a sync or async function is executed.
- When there is no microTask scheduled.
Reactive forms is a model-driven approach for creating forms in a reactive style(form inputs changes over time). These are built around observable streams, where form inputs and values are provided as streams of input values. Let's follow the below steps to create reactive forms,
- Register the reactive forms module which declares reactive-form directives in your app
import { ReactiveFormsModule } from '@angular/forms'; @NgModule({ imports: [ // other imports ... ReactiveFormsModule ], }) export class AppModule { }
- Create a new FormControl instance and save it in the component.
import { Component } from '@angular/core'; import { FormControl } from '@angular/forms'; @Component({ selector: 'user-profile', styleUrls: ['./user-profile.component.css'] }) export class UserProfileComponent { userName = new FormControl(''); }
- Register the FormControl in the template.
Finally, the component with reactive form control appears as below,<label> User name: <input type="text" [formControl]="userName"> </label>
import { Component } from '@angular/core'; import { FormControl } from '@angular/forms'; @Component({ selector: 'user-profile', styleUrls: ['./user-profile.component.css'] template: ` <label> User name: <input type="text" [formControl]="userName"> </label> ` }) export class UserProfileComponent { userName = new FormControl(''); }
Dynamic forms is a pattern in which we build a form dynamically based on metadata that describes a business object model. You can create them based on reactive form API.
Template driven forms are model-driven forms where you write the logic, validations, controls etc, in the template part of the code using directives. They are suitable for simple scenarios and uses two-way binding with [(ngModel)] syntax. For example, you can create register form easily by following the below simple steps,
- Import the FormsModule into the Application module's imports array
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import {FormsModule} from '@angular/forms' import { RegisterComponent } from './app.component'; @NgModule({ declarations: [ RegisterComponent, ], imports: [ BrowserModule, FormsModule ], providers: [], bootstrap: [RegisterComponent] }) export class AppModule { }
- Bind the form from template to the component using ngModel syntax
<input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name">
- Attach NgForm directive to the
- Apply the validation message for form controls
<label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Please enter your name </div>
- Let's submit the form with ngSubmit directive and add type="submit" button at the bottom of the form to trigger form submit.
Finally, the completed template-driven registration form will be appeared as follow.<form (ngSubmit)="onSubmit()" #heroForm="ngForm"> // Form goes here <button type="submit" class="btn btn-success" [disabled]="!registerForm.form.valid">Submit</button>
<div class="container"> <h1>Registration Form</h1> <form (ngSubmit)="onSubmit()" #registerForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Please enter your name </div> </div> <button type="submit" class="btn btn-success" [disabled]="!registerForm.form.valid">Submit</button> </form> </div>
Below are the main differences between reactive forms and template driven forms | Feature | Reactive | Template-Driven | |---- |---- | --------- | | Form model setup | Created(FormControl instance) in component explicitly | Created by directives | | Data updates | Synchronous | Asynchronous | | Form custom validation | Defined as Functions | Defined as Directives | | Testing | No interaction with change detection cycle | Need knowledge of the change detection process | | Mutability | Immutable(by always returning new value for FormControl instance) | Mutable(Property always modified to new value) | | Scalability | More scalable using low-level APIs | Less scalable using due to abstraction on APIs|
You can use patchValue()
method to update specific properties defined in the form model. For example,you can update the name and street of certain profile on click of the update button as shown below.
updateProfile() {
this.userProfile.patchValue({
firstName: 'John',
address: {
street: '98 Crescent Street'
}
});
}
<button (click)="updateProfile()">Update Profile</button>
You can also use setValue
method to update properties.
Note: Remember to update the properties against the exact model structure.
FormBuilder is used as syntactic sugar for easily creating instances of a FormControl, FormGroup, or FormArray. This is helpful to reduce the amount of boilerplate needed to build complex reactive forms. It is available as an injectable helper class of the @angular/forms
package.
For example, the user profile component creation becomes easier as shown here.
export class UserProfileComponent {
profileForm = this.formBuilder.group({
firstName: [''],
lastName: [''],
address: this.formBuilder.group({
street: [''],
city: [''],
state: [''],
zip: ['']
}),
});
constructor(private formBuilder: FormBuilder) { }
}
You can add a getter property(let's say, diagnostic) inside component to return a JSON representation of the model during the development. This is useful to verify whether the values are really flowing from the input box to the model and vice versa or not.
export class UserProfileComponent {
model = new User('John', 29, 'Writer');
// TODO: Remove after the verification
get diagnostic() { return JSON.stringify(this.model); }
}
and add diagnostic
binding near the top of the form
{{diagnostic}}
<div class="form-group">
// FormControls goes here
</div>
In a model-driven form, you can reset the form just by calling the function reset()
on our form model.
For example, you can reset the form model on submission as follows,
onSubmit() {
if (this.myform.valid) {
console.log("Form is submitted");
// Perform business logic here
this.myform.reset();
}
}
Now, your form model resets the form back to its original pristine state.
Sometimes you may need to both ngFor and ngIf on the same element but unfortunately you are going to encounter below template error.
Template parse errors: Can't have multiple template bindings on one element.
In this case, You need to use either ng-container or ng-template. Let's say if you try to loop over the items only when the items are available, the below code throws an error in the browser
<ul *ngIf="items" *ngFor="let item of items">
<li></li>
</ul>
and it can be fixed by
<ng-container *ngIf="items">
<ul *ngFor="let item of items">
<li></li>
</ul>
</ng-container>