Features

  • Strictly Typed Forms
  • Standalone Components
  • Page Title Strategy
  • Protected variables
  • Extended developer diagnostics
  • More built-in improvements
  • inject API
  • etc.

pankajparkar

Pankaj P. Parkar

Sr. Technology Consultant, Virtusa

  • ex MS MVP (2016 - 22)

  • Angular GDE

  • Stackoverflow Topuser

About Me!

pankajparkar

Simple Form Example

@Component({
  selector: 'app-typed-forms',
  templateUrl: './typed-forms.component.html',
  styleUrls: ['./typed-forms.component.css'],
})
export class TypedFormsComponent {
  userForm = new FormGroup({
    name: new FormControl(''),
    age: new FormControl(''),
    address: new FormGroup({
      city: new FormControl(),
      state: new FormControl(),
      postalCode: new FormControl(),
    }),
  });
  ngOnInit() {
    const value = this.userForm.value;
    const { name, age, addres } = value;
    const { city, state, postalCode } = addres;
  }
}
type UserForm {
  name: string;
  age: string;
  address: {
    city: string;
    state: string;
    postalCode: string;
  }
}

Strictly Typed Form

 userForm = new FormGroup({
   name: new FormControl(''),
   age: new FormControl(''),
   address: new FormGroup({
      city: new FormControl(),
      state: new FormControl(),
      postalCode: new FormControl(),
   }),
 });
userForm = new FormGroup({
  name: new FormControl<string>(''),
  age: new FormControl<number | null>(null),
  address: new FormGroup({
    city: new FormControl<string>(''),
    state: new FormControl<string>(''),
    postalCode: new FormControl<number | null>(null),
  }),
});

Strictly Typed Form

 userForm = new FormGroup({
   name: new FormControl(''),
   age: new FormControl(''),
   address: new FormGroup({
      city: new FormControl(),
      state: new FormControl(),
      postalCode: new FormControl(),
   }),
 });
interface Address {
  city?: FormControl<string>;
  state?: FormControl<string>;
  postalCode?: FormControl<string>;
}
interface UserForm {
  name?: FormControl<string>;
  age?: FormControl<string>;
  address?: FormGroup<Address>;
}
userForm = new FormGroup<UserForm>({
  name: new FormControl(),
  age: new FormControl(),
  address: new FormGroup<Address>({
    city: new FormControl(),
    state: new FormControl(),
    postalCode: new FormControl(),
  }),
});

submit() {
  console.log(this.userForm.value);
  const { address, age, name } = this.userForm.value;
}
 userForm = new UntypedFormGroup({
   name: new UntypedFormControl(''),
   age: new UntypedFormControl(''),
   address: new UntypedFormGroup({
      city: new UntypedFormControl(),
      state: new UntypedFormControl(),
      postalCode: new UntypedFormControl(),
   }),
 });

Strictly Typed Form - Fallback

Standalone Components 

(developer preview)

  • Stepping towards Optional NgModule now
  • The component can be defined without NgModule
ng generate component standalone --standalone 
@Component({
  selector: 'app-standalone',
  standalone: true,
  imports: [CommonModule], // <-- all imports goes here
  templateUrl: './standalone.component.html',
  styleUrls: ['./standalone.component.scss']
})
export class StandaloneComponent implements OnInit {
  ...
}

pankajparkar

How angular application Works?

.

.

.

Root

Component

// app.module.ts
@NgModule({
  declarations: [
    AppComponent,
    ...
  ],
  bootstrap: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    ...
  ],
  providers: [],
})
export class AppModule { }
// main.ts
platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch(err => console.error(err));

C

P

S

D

M

Standalone Components 

ctd.


@NgModule({
  declarations: [AppComponent],
  bootstrap:    [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
  ],
  providers: [],
})
export class AppModule { }

Bootstrap app without AppModule using `bootstrapApplication`

@Component({
  selector: 'app-root',
  standalone: true, // <- add standalone
  imports: [], // <- and imports array
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
}
bootstrapApplication(
  AppComponent, 
  {
    providers: [
      importProvidersFrom([
        BrowserModule,
        AppRoutingModule,
        BrowserAnimationsModule,
      ])
    ]
  }
)

Standalone Components 

ctd.

const routes = [
  {
    path: 'standalone',
    component: StandaloneComponent,
  },
];
const routes = [
  {
    path: 'standalone-lazy',
    loadComponent: () => import('./components/standalone-lazy.component')
    	.then(s => s.StandaloneLazyComponent),
  },
]

Routing

Lazy Standalone Component

pankajparkar

Standalone Components 

ctd.

const routes = [{
  path: 'standalone-childrens',
  loadComponent: () => import('./components/standalone-childrens.component')
    .then(s => s.StandaloneChildrenComponent),
  children: [
    {
      path: 'child-firstchild',
      component: import('./components/standalone-first-child.component')
      	.then(s => s.FirstChildComponent)
    },
    {
      path: 'child-firstchild',
      component: import('./components/standalone-first-child.component')
      	.then(s => s.FirstChildComponent)
    },
  ]
}]
Lazy Standalone Component and its children routes

pankajparkar

Standalone everything 

pankajparkar

  • Components
  • Directives
  • Pipes

Page Title Strategy - a11y

Route Level (hard coded)

const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    title: 'Dashboard',
  },
  {
    path: 'typed-forms',
    component: TypedFormsComponent,
    title: 'Typed Forms'
  },
  {
    path: 'standalone-component',
    component: StandaloneComponent,
    title: 'Standalone Component',
  },
  ...
  
 ];

pankajparkar

Page Title Strategy - a11y

Route Level (dynamic)

@Injectable({ providedIn: 'root' })
export class DynamicTitleService implements Resolve<string> {

  constructor() { }

  async resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    const post = await fetch('https://jsonplaceholder.typicode.com/posts/1')
      .then((response) => response.json());
    return post.title;
  }
}

pankajparkar

[
  {
    path: 'dashboard',
    component: DashboardComponent,
    title: DynamicTitleService
  },
  ...
]

Page Title Strategy - a11y

Global App Level strategy

@Injectable({ providedIn: 'root' })
export class TitleStrategyService extends TitleStrategy {
  constructor(private readonly title: Title) {
    super();
  }

  override updateTitle(routerState: RouterStateSnapshot) {
    const title = this.buildTitle(routerState);
    if (title !== undefined) {
      this.title.setTitle(`Angular 14 | ${title}`);
    }
  }
}

pankajparkar

{
  provide: TitleStrategy,
  useClass: TitleStrategyService,
}

Protected variable

@Component({
  selector: 'app-protected-variable',
  template: `
    <h1>
      Protected: {{ message }}
    </h1>
  `,
})
export class ProtectedVariableComponent {
  
  protected message = 'My Protected Varaible';

}

pankajparkar

Extended developer diagnostics

{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      // The categories to use for specific diagnostics.
      "checks": {
        // Maps check name to its category.
        "invalidBananaInBox": "error"
        "nullishCoalescingNotNullable": "error"
      },
      // The category to use for any diagnostics
      // not listed in `checks` above.
      "defaultCategory": "suppress"
    },
    ...
  },
  ...
}

pankajparkar

More built-in improvements

  • Take advantage of Typescript 4.7 which targets ES2020
  • Optional injectors in Embedded Views
this.viewContainerRef.createEmbeddedView(
  templateRef, 
  context,
  { injector }
)
const templateRef = this.template.createEmbeddedView({ 
  $implicit: user
}, { injector });
this.vc.insert(templateRef);

pankajparkar

More built-in improvements

  • New Primitives for Angular CDK Dropdown and Menu

Inject() 💉

Reference: https://nartc.me/blog/inheritance-angular-inject

It takes an instance of dependency from the current injector tree

@Component(...)
abstract class TestComponent {
  constructor(
    private a: A
  ) { }
}
@Component(...)
abstract class TestComponent {
  a = Inject(A); // <- ✅
}
@Component(...)
abstract class TestComponent {
  a: A|undefined;

  constructor() {
    this.a = inject(A); // <- ✅
  }
}
@Component(...)
abstract class TestComponent {
  a: A|undefined;
 
  ngOnInit() {
    this.a = inject(A); // <- ❌
  }
}

Inject() 💉

Reference: https://nartc.me/blog/inheritance-angular-inject

@Directive()
abstract class Base {
  a = inject(A);
}

@Component()
class Cmp extends Base {
  constructor(b: B) {
    super();
  }
}
@Directive()
abstract class BaseClass {
  constructor(private a: A) {
  }
}

@Component()
class Cmp extends BaseClass {
  constructor(b: B, a: A) {
    super(a);
  }
}

pankajparkar

Inject() 💉

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<div>{{ state.counter }}</div>
    <div>
      <button (click)="state.counter = 0">RESET</button>
      <button (click)="increment()">+</button>
    </div>`,
})
export class CounterComponent {
  state = injectState({
    counter: 0,
  });

  increment() {
    this.state.counter += 1;
  }
}

pankajparkar

Inject() 💉

Blog - https://marmicode.io/blog/angular-inject-and-injection-functions

function injectState() {

  /* @hack wrongly assuming that ChangeDetectorRef is a ViewRef
   * Yeah! CDR is actually the ViewRef of the component. */
  const viewRef = inject(ChangeDetectorRef) as ViewRef;

  /* Initialize state. */
  const state = new RxState();
  state.set(initialState);

  queueMicrotask(() => 
    viewRef.onDestroy(() => state.ngOnDestroy())
  );
}

pankajparkar

Demo - https://github.com/pankajparkar/angular-14

pankajparkar

Q & A

pankajparkar

pankajparkar

pankajparkar

References

  • https://blog.angular.io/angular-v14-is-now-available-391a6db736af

  • https://netbasal.com/handling-page-titles-in-angular-40b53823af4a

  • https://blog.angular.io/angular-extended-diagnostics-53e2fa19ece9

  • https://marmicode.io/blog/angular-inject-and-injection-functions

  • https://nartc.me/blog/inheritance-angular-inject

pankajparkar

What's new in Angular 14?

v14

Made with Slides.com