Angular Array Filter Framework

We are currently working on an Angular application that has many different types of domain specific objects all of which have a common set of base attributes, “isDeleted” and “title” to name a few.

[code language=”javascript”]
export interface EntityDomainModelBase {
code: string;
title?: string;
isDeleted?: boolean;
}
[/code]

There are a few areas within the application that will need to filter arrays of the domain specific objects by both some domain specific attributes and also by the base attributes.  A base Angular “pipe” class was created to encapsulate the plumbing of the filter and the filter logic for the common base attributes, along with the delegation to the domain specific subclass.

[code language=”javascript”]
import { PipeTransform } from ‘@angular/core’;

import { EntityDomainModelBase } from ‘./api/DomainModelBase’;

export class DomainModelArrayFilterPipeContext {
viewDeleted: boolean;
searchTerm: string;
}

export class DomainModelArrayFilterPipe<typeOfEntity extends EntityDomainModelBase>
implements PipeTransform
{
transform(entities: typeOfEntity[], ctx: DomainModelArrayFilterPipeContext) {

if (!ctx)
return entities;
if (!entities || !entities.length)
return entities;

return entities.filter((item: typeOfEntity) => {
return (ctx.viewDeleted == undefined || item.isDeleted == ctx.viewDeleted)
&& (ctx.searchTerm == undefined || ctx.searchTerm == ”
|| item.title.toLowerCase().indexOf(ctx.searchTerm.toLowerCase()) >= 0
|| this.checkTerm(item, ctx.searchTerm)
);
});
}

/*
implement in domain specific filter class to filter on domain specific attributes
*/
checkTerm(item: typeOfEntity, searchTerm: string): boolean { return true; }
}
[/code]

A domain specific subclass was then created and used like any other “@pipe” attributed class. This domain specific pipe implementation also filters through an array of persons for a specific name.

[code language=”javascript”]
@Pipe({ name: ‘domainSpecificObjectFilter’ })
export class DomainSpecificFilterPipe extends DomainModelArrayFilterPipe<SomeDomainModel> {
checkTerm(item: SomeDomainModel, searchTerm: string): boolean {
return item.persons.filter(p => p.name.toLowerCase().indexOf(searchTerm.toLowerCase()) >= 0).length > 0;
}
}
[/code]

The filter classes above are then used to filter a list of domain specific objects and draw the appropriate HTML.

[code language=”html”]
<ng-container *ngFor="let event of (events | eventsFilter:filterContext)">
[/code]

With this design any number of domain specific filtering scenarios can be addressed without having to duplicate the common argument checking and array filtering logic.

Angular Dirty Tracking of Object Graphs

I am currently working on a CRUD app in Angular and needed to implement dirty tracking of an object graph.  All the examples I have found work well on the “input” properties to a component, or for simple objects using the “KeyValueDiffer”.

I started implementing the logic based upon this post Angular Change Detection but As soon as I tried to edit a collection or property of the root object I would need to do traverse the nested objects and arrays.

I then decided to keep it simple and just compare JSON strings of the original object and any changes that occur to it during the edit cycle.  This approach would allow the user to change a property back to it’s initial state using normal editing procedures.  Editing an “input” and then just changing the text back would reset the dirty, adding a collection item then removing it after add would reset the dirty, etc.

[code language=”javascript”]
updateDirty(): boolean {

var currentJson: string = JSON.stringify(this.entity);
this.isDirty = currentJson != this.originalJson;

return this.isDirty;
}

editStart(entity: typeOfEntity) {
this.entity = entity;
this.isDirty = false;

this.originalJson = JSON.stringify(entity);
}
[/code]

Angular Constants Injected Server Side from ASP.NET CSHTML

Most answers on the internet work well for JIT compilation, refer to the stack overflow article here;  Stack Overflow Question

We are using JIT compilation for development and AOT for deployment. I couldn’t find a good way to make the standard approach work with AOT. For reference I followed the AOT cookbook here… Angular AOT cookbook.

The approach below will not work for server side rendering but should suffice for both JIT and AOT client side rendering.

1) In the razor view, _layout.cshtml for example, just put a script block and set a JSON object on the window interface. I placed this block within the “head” tag but doesn’t really matter. The values for the JSON keys can be determined by any razor syntax.

[code language=”javascript”]
window.appContext = {
userName: ‘@("test".ToUpper())’,
isAdmin: @(1 == 1 ? "true" : "false")
};
[/code]

2) Create an app context service and wire up in appropriate module and inject into components for usage, if you need help with this just comment and I’ll supply more info.

[code language=”javascript”]
import { Injectable } from ‘@angular/core’;

/*
must match definition in SCRIPT tag that is seeded from razor
*/
interface IAppContext {
userName: string;

isAdmin: boolean;
}

/*

*/
@Injectable()
export class AppContextService implements IAppContext {

userName: string;

isAdmin: boolean;

constructor() {
var appContextBootstrap: IAppContext = (<IAppContext>(<any>window).appContext);

this.userName = appContextBootstrap.userName;
this.isAdmin = appContextBootstrap.isAdmin;
}

}
[/code]

3) reference and use the app context service throughout the application.

[code language=”javascript”]
import { Component } from ‘@angular/core’;

import { AppContextService } from ‘./appContext.service’

@Component({
moduleId: module.id,
selector: ‘story-app’,
templateUrl: ‘app.component.html’
})
export class AppComponent {
AppConfig: any;
constructor(private appContext: AppContextService) { }

ngOnInit() {

alert(‘hi – ‘ + this.appContext.userName);
}
}
[/code]