Latest Angular 14 Installation, Features & Updates
How to install Angular 14?
Angular 14 could be installed via npm using the next flag.
Simply open a new command line interface and run the following command to install the latest version of Angular:
npm install --global @angular/cli@next
This will install the latest version of Angular CLI globally on your development machine.
Check the angular CLI version using below command
ng version
Angular 14 Features
1. Standalone Components
With the release of Angular 14, standalone components will, at last, be a feasible option, and Angular modules will no longer be required.
A standalone component is not declared in any existing NgModule, and it directly manages its own dependencies (instead of having them managed by an NgModule) and can be depended upon directly, without the need for an intermediate NgModule.
Key Points
- Standalone component has the flag "standalone" we need to set the value true.
- Not Required to add the standalone component in ngModule.
- We can import the required modules in the component itself.
- Command,
ng g c standalonedemo --standalone
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-standalonedemo',
standalone: true,
imports: [CommonModule],
templateUrl: './standalonedemo.component.html',
styleUrls: ['./standalonedemo.component.css']
})
export class StandalonedemoComponent implements OnInit {
constructor() {}
ngOnInit(): void {}
}
<div class="container pages text-justify pb-5">
<h1 class="mb-4">Angular 14 Standalone Component</h1>
<p>standalonedemo works!</p>
</div>
2. Typed Forms
The most common request for an Angular feature on GitHub is for strictly typed forms, which, would improve the framework’s model-driven approach to the process of dealing with forms.
Key Points
- This feature is only for reactive forms.
- For using this feature tsconig.js should be in strict mode.
- Typed forms ensure that the values inside of form control, groups and array are type safe across the entire API surface.
- This helps developer for generating safer forms and it helps in case of complex nested object.
- If you want to use the older version you can use the untyped version.
E.g. create a contact us standalone component and add 4 fields in it (name, number, email, message) and import ReactiveFormsModule in it as shown below,
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
@Component({
selector: 'app-contactus',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
templateUrl: './contactus.component.html',
styleUrls: ['./contactus.component.css']
})
export class ContactusComponent implements OnInit {
constructor() {}
ngOnInit(): void {}
contactForm = new FormGroup({
name: new FormControl < string > (''),
email: new FormControl(''),
message: new FormControl(''),
number: new FormControl()
});
contactFormUntyped = new UntypedFormGroup({
name: new UntypedFormControl(''),
email: new FormControl(''),
message: new FormControl(''),
number: new FormControl()
});
Submit() {
console.log(this.contactForm.value);
console.log(this.contactForm.value.email?.length); //check the null value
console.log(this.contactForm.value.email!.length); //If you are sure value is not null
console.log(this.contactFormUntyped.value.name.length); // throw error in browser
}
}
<div class="container pages text-justify pb-5">
<h1 class="mb-4">Angular 14 Typed Form</h1>
<section class="mb-4">
<!--Section heading-->
<div class="row">
<!--Grid column-->
<div class="col-md-9 mb-md-0 mb-5">
<form [formGroup]="contactForm" (ngSubmit)="Submit()">
<!--Grid row-->
<div class="row">
<!--Grid column-->
<div class="col-md-6">
<div class="md-form mb-0">
<input formControlName="name" type="text" class="form-control">
<label for="name" class="">Your name</label>
</div>
</div>
<!--Grid column-->
<!--Grid column-->
<div class="col-md-6">
<div class="md-form mb-0">
<input formControlName="email" type="text" class="form-control">
<label for="email" class="">Your email</label>
</div>
</div>
<!--Grid column-->
</div>
<!--Grid row-->
<!--Grid row-->
<div class="row">
<div class="col-md-12">
<div class="md-form mb-0">
<input formControlName="number" type="text" class="form-control">
<label for="subject" class="">Number</label>
</div>
</div>
</div>
<!--Grid row-->
<!--Grid row-->
<div class="row">
<!--Grid column-->
<div class="col-md-12">
<div class="md-form">
<textarea formControlName="message" type="text" rows="2" class="form-control md-textarea"></textarea>
<label for="message">Your message</label>
</div>
</div>
</div>
<!--Grid row-->
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
<!--Grid column-->
</div>
</section>
</div>
3. Streamlined page title accessibility (Title Strategy)
Your page title shows the content of your page differently when you are developing applications. In Angular 13, the entire process of adding titles was streamlined with the fresh Route.title property in the Angular router. However, Angular 14 doesn't have more additional imports needed when you are adding a title to your page.
Go to the routing module where we define our routes now. We have a feature to add the title in the route and implement the TitleStrategy by extending the approuting module as shown.
import { NgModule } from '@angular/core';
import { RouterModule, RouterStateSnapshot, Routes, TitleStrategy } from '@angular/router';
import { ContactusComponent } from './contactus/contactus.component';
import { HomeComponent } from './home/home.component';
import { StandalonedemoComponent } from './standalonedemo/standalonedemo.component';
const routes: Routes = [
{path:'',component:HomeComponent, title : "Core Knowledge Sharing"},
{ path: 'standalonedemo', component: StandalonedemoComponent, title : "Stand alone Component" },
{ path: 'contactus', component: ContactusComponent, title : "Contact Us" }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule extends TitleStrategy {
updateTitle(snapshot: RouterStateSnapshot): void {
const pageTitle = this.buildTitle(snapshot);
if(pageTitle != undefined){
document.title = "${pageTitle}"
}
}
}
4. Extended developer diagnostics (ng compilation)
This feature from Angular v14 delivers an extendable framework that assists better insights into your templates and offers suggestions for potential boosts. . It also checks the syntax error in our component like in contact us component you remove the reactive
E.g. you type the wrong command like ng sevre then it will give suggestion for right commands
5. Bind to protected component members
In v14, you can now bind to protected component members directly from your templates, thanks to a contribution from Zack Elliott!
@Component({
selector: 'my-component',
template: '{{ message }}', // Now compiles!
})
export class MyComponent {
protected message: string = 'Hello world';
}
6. Optional Injectors in Embedded Views
v14 adds support for passing in an optional injector when creating an embedded view through ViewContainerRef.createEmbeddedView
and TemplateRef.createEmbeddedView
. The injector allows for the dependency injection behavior to be customized within the specific template.
This enables cleaner APIs for authoring reusable components and for component primitives in Angular CDK.
viewContainer.createEmbeddedView(templateRef, context, {
injector: injector,
})
7. NgModel OnPush
And finally, a community contribution by Artur Androsovych closes a top issue and ensures that NgModel changes are reflected in the UI for OnPush components.
I have two components: the first one is a simple child component with some input and basic support of [ngModel]
directive via ControlValueAccesor
interface. The second one is a parent component that has onPush
change detection strategy and populates a value into child component via [ngModel]
directive (right after that it's marked for changes via ChangeDetectorRef
class). When [ngModel]
is updated child component still displays an old value in the template while the component object has actual value in target property.
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
@Component({
selector: 'app-onpushdemo',
template: `<div>
<app-child [ngModel]="value"></app-child>
</div>`,
styleUrls: ['./onpushdemo.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OnpushdemoComponent implements OnInit {
public value: any = "old value";
constructor(private _changeDetector: ChangeDetectorRef) {
setTimeout(() => {
debugger;
this.value = "new value";
this._changeDetector.markForCheck();
}, 5000)
}
ngOnInit(): void {}
}
import { Component, forwardRef, OnInit } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'app-child',
template: `<div (click)="click()">Current Value: {{value}}.
Click me to force change detection</div>`,
styleUrls: ['./child.component.css'],
inputs: ['value'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ChildComponent),
multi: true
}],
})
export class ChildComponent implements OnInit {
constructor() {}
public value: any;
public onChange: (_: any) => void = (_: any) => {
// do nothing
};
public onTouched: () => void = () => {
// do nothing
};
public writeValue(value: any): void {
this.value = value;
}
public click(): void {
debugger;
this.value = "child new value";
// event just forces angular to re run change detection
}
public registerOnChange(fn: (_: any) => void): void {
this.onChange = fn;
}
public registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
ngOnInit(): void {}
}