@mediacologne/ng-data

Stats

StarsIssuesVersionUpdatedCreatedSize
@mediacologne/ng-data
10.0.1a year ago3 years agoMinified + gzip package size for @mediacologne/ng-data in KB

Readme

@mediacologne/ng-data

News

  • Achtung! Dies ist eine Angular >= 6 Library!

Installation

Install the NPM Module

$ npm install @mediacologne/ng-data

Using the Library

Nach der Installation muss die Library durch das Importieren des DataModule verfügbar gemacht werden.

// Import your library
import { DataModule } from '@mediacologne/ng-data';

@NgModule({
  imports: [
    DataModule.forRoot({
        store: {
            subscriptions: {
                unsubscribe: false
            }
        }
    })
  ]
})
export class AppModule { }

Documentation

Configuration via forRoot

Wahrscheinlich wird bei Verwendung des Modules die fromObservable() Methode genutzt. Dieser wird i.d.R. ein HttpClient request übergeben, welcher intern in dem Module subscribed wird. Mit Hilfe der configuration store.subscribtions.unsubscribe (default = false) kann spezifiziert werden, dass diese Requests nach einem erfolgreichen subscribe() sofort wieder unsubscribed() werden sollen.

Concepts

Es folgt eine kurze Erklärung über die Konzepte der einzelnen Komponenten dieser Library.

StoreManager

Der StoreManager kümmert sich um die Instanziierung eines DataStores bzw. um die Verwaltung der DataStore Instanzen. Alle über den StoreManager erzeugten DataStores werden von diesem in einem Service Container vorgehalten.


DataStore

Der DataStore ist das zentrale Element der Library. Innerhalb des DataStores werden alle Daten gehalten. Jeder DataStore verfügt über Methoden zum Hinzufügen, Löschen, Updaten von Daten.

Injectable DataStores

In version 8.1.0 support for injectable DataStores was added.
The injectable DataStore allows you to get an (singleton) instance of a DataStore injected in your constructor. To use this feature, you have to provide the store via your Modules provides section.
Let's see an example:

Provide your injectable DataStore with the DatastoreFactory

@NgModule({
  declarations: [],
  imports: [],
  providers: [
      {
          provide: 'CustomerStore',
          useFactory: DataStoreFactory.create('customer')
      }
  ],
})
export class AppModule {
}

Inject the DataStore in your constructor

export class AppComponent {
    constructor(@Inject('CustomerStore') private customerStore: DataStore) {
        // Hint: no need to call StoreManager.get('customer')
    }
}

Generic Types for DataStore<T>

In version 8.1.0 support for Generics (<T>) was added to the DataStore. You can decide for yourself if you want to use the new feature or not. In both cases this is not a breaking change.
The benefits of using the new Generics is type safety and intellisense on all DataStore operations.
Let's see an example:

interface Customer {
    id: number;
    name: string
}
export class AppComponent {
    // Use Generics <T> with the new injectable DataStores 
    constructor(@Inject('CustomerStore') private customerStore: DataStore<Customer>) {
    }
    
    // Or use the StoreManager to get an instance
    public customerStore: DataStore<Customer>;     
    constructor() {
        this.customerStore = StoreManager.get('customer');
    }
}

Predicate

Der DataStore nutzt zur Identifikation von DataItems standardmäßig die id Property. Diese wird verwendet um bspw. beim Hinzufügen (Method: add() ) von DataItems zu erkennen, ob dieses DataItem bereits im DataStore ist und geupdated werden soll anstatt erneut hinzuzufügen.

Wenn die DataItems über keine id Property verfügen, entstehen Duplikate. In diesem Fall kann über den StoreManager ein IdentityPredicate übergeben werden. Dieses überschreibt den internen Wert der IdentityPredicate.

this.identityLessStore = StoreManager.get('identityLess', {
    identityPredicate: "lastname" // Property 'lastname' should be used to identify DataItems
});

Queries

Queries bieten eine Möglichkeit, innerhalb der Daten eines DataStores zu Suchen. Eine Besonderheit der Suchmöglichkeiten sind die StoredQueries, welche einerseits zum Zeitpunkt der Suche ausgeführt werden, als auch ein weiteres Mal ausgeführt werden sobald sich das Suchergebnis verändert haben könnte. Wird beispielsweise über eine Queries ein bestimmtes Objekt gesucht welches sich nicht im DataStore befindet, wird die Queries automatisch ein weiteres Mal ausgeführt sobald sich neue Daten im Store befinden.


DataStoreView

Mit Hilfe der DataStoreViews werden die Daten eines DataStores auf verschiedene Weisen/Sichten representiert. Der DataStoreView wird eine DataStore Instanz zugewiesen, auf welcher die DataStoreView anschließend operiert. Die DataStoreView bietet sogenannte storeFilter an.

StoreFilter

Mit Hilfe von storeFilters können die Daten eines DataStores auf verschiedene Sichten representiert werden. Eine storeFilter wird wie ein Filter über die Daten gelegt und steht anschließend innerhalb von Typescript als auch den Templates über eine spezielle StoreFilterPipe zur Verfügung. Der Unterschied zwischen StoreFilters und Queries ist im wesentlichen, dass Queries einmalig ausgeführt werden um ein bestimmtes Ergebnis zu erhalten während ein StoreFilter vorzugsweise innerhalb eines Templates verwendet wird


ImmutableStoreAdaptor (für ngx-datatable)

Der ImmutableStoreAdapter wird als Adapter über einen normalen DataStore gelegt und bietet ebenso die Methoden asArray() als auch asObservable(). Der entscheidende Unterschied zwischen den Methoden des DataStore's und ImmutableStoreAdaptor liegt in der immutabilität der Daten beim ImmutableStoreAdaptor.

Immutable Daten werden zum Beispiel bei Componenten benötigt, welche auf die changeDetection: ChangeDetectionStrategy.OnPush von Angular setzen und eine Änderung der Daten im DataStore daher nicht mitbekommen können. Durch den ImmutableStoreAdaptor werden Änderungen am DataStore erkannt und als geklontes Array (mittels ES6 Syntax) [...dataStore.asArray()] zurück gegeben.

ngx-datatable Searching

Der häufigste Verwendungszweck des ImmutableStoreAdaptors ist wahrscheinlich die Verwendung mit einer @swimlane/ngx-datatable. Für durchsuchbare Tabellen bietet der ImmutableStoreAdaptor ein paar Besonderheiten bzw. Features.

Beispiel
Hier wird ein Suchfeld über der Tabelle definiert dessen value der .search() Methode des customerStoreAdaptor übergeben wird.

<input type="text" #searchInput (keyup)="customerStoreAdaptor.search(searchInput.value)">
<ngx-datatable [rows]="(customerStoreAdaptor.asObservable() | async)">
    <!-- Column definitions.... -->
</ngx-datatable>

Search Complete Promise Die customerStoreAdaptor.search(searchInput.value) gibt einen Promise zurück, welcher resolved sobald die Suche abgeschlossen ist und liefert als Parameter die gefilterten Einträge. Dies ist sinnvoll, wenn die .search() Methode programmatisch aufgerufen wird und auf das Ergebnis der Suche gewartet werden möchte.

this.customerStoreAdaptor.search(searchQuery).then((filteredItems: any[]) => { /* do something */ });

Dem Constructor (bzw. der setSearchFn()) des ImmutableStoreAdaptors kann ein Argument vom Typ searchFn | ISearchConfig übergeben werden. Siehe nächste Abschnitte für weitere Informationen

SearchFn

Wird eine Function vom Typ searchFn übergeben, wird diese bei einer Änderung des Suchbegriffs automatisch ausgeführt und erhält als Paramater einzeln die Einträge des DataStores sowie dem eingegeben Suchbegriff. Die searchFn wird also für jedes Item im DataStore aufgerufen.
Die Eingabe des Suchbegriffs wird automatisch mittels debounceTime(300) und distinctUntilChange verzögert (sofern der Suchbegriff keinem leeren String entspricht).
Die searchFn könnte z.B. wie folgt aussehen:

new ImmutableStoreAdaptor(this.customerStore, (item: any, searchQuery: string) => {
  return item.name.toLowerCase().indexOf(searchQuery) !== -1;
});

SearchFn via ISearchConfig

Anstatt die searchFn direkt als Argument zu übergeben, kann diese auch via ISearchConfig übergeben werden.
Dies bietet den Vorteil, dass zusätzliche Konfigurationen übergeben werden können.

Als zusätzliche Option steht in Kombination mit der searchFn derzeit nur debounceTime zur Verfügung. Hiermit kann die Zeit in Milisekunden angegeben werden bzw. mittels Wert 0 komplett deaktiviert werden (falls kein debouncing gewünscht ist (nicht empfohlen!)).

new ImmutableStoreAdaptor(this.customerStore, {
  searchFn: (item: any, searchQuery: string) => {
      return item.name.toLowerCase().indexOf(searchQuery) !== -1;
  },
  debounceTime: 300 // additionalConfig
});

searchProperties via ISearchConfig

Für die meisten Tabellen reichen einfache Suchalgorithmen aus, bei welchen einzelne Properties der Items im DataStore durchsucht werden sollen. Dies resultiert in der Praxis aus immer (fast) identisch aussehenden searchFn. Aus diesem Grund gibt es die searchProperties!

Hinweis: searchProperties kann nicht verwendet werden wenn eine searchFn definiert wurde!

new ImmutableStoreAdaptor(this.customerStore, {
  searchProperties: ['name', 'address.city', 'contact'],
  debounceTime: 300 // additionalConfig
});

Werden searchProperties angegeben, findet die Suche automatisch nur innerhalb dieser Properties statt. Wir stellen uns vor, der DataStore wäre mit Kunden folgender Datenstruktur gefüllt:

interface Customer {
    name: string,
    logo: string,
    address: {
        city: string,
        zipcode: number
    },
    contact: {
        phone: string,
        email: string,
        contactPerson: {
            name: string,
            gender: string
        }
    }
}

Davon ausgehend, dass die searchProperties wie folgt definiert wurden: ['name', 'address.city', 'contact'] werden nun folgende Properties durchsucht: name, address.city, contact.phone, contact.email.
Hinweis: Handelt es sich bei einer Property um ein Objekt (z.B. contact), werden dessen Sub-Properties komplett durchsucht. Allerdings findet hier keine rekursion statt! Deshalb wird contactPerson oder dessen Sub-Properties (nameoder gender) nicht durchsucht!

Special Formatter

Für jede searchProperty kann, wenn gewünscht, optional ein formatter angegeben werden, welche vor dem Durchsuchen der Property aufgerufen wird.
Hier ist ein Beispiel, bei dem Umlaute ersetzt werden. Köln würde man nun nur noch mittels Koeln schreibweise finden.

searchProperties: [
    'name',
    {name: 'address.city', formatter: (value: any) => { return value.replace('ö', 'oe') }},
    'address.zipcode',
    'start'
]

compareFn via ISearchConfig

Werden bei der Konfiguration die searchProperties angegeben, basiert die Durchsuchung des Suchbegriffs innerhalb der Properties standardmäßig auf einem indexOf() !== -1.

Über die compareFn lässt sich eine Function angeben, welche Suche durchführen soll. Dabei wird diese Function pro Item, pro definierter Property einzeln aufgerufen (Anzahl Items x Anzahl searchProperties), daher sollte diese relativ performant sein!

Beispiel

new ImmutableStoreAdaptor(this.customerStore, {
  searchProperties: ['name', 'address.city', 'contact'],
  compareFn: (value: any, searchQuery: string) => value.indexOf(searchQuery) !== -1 // Default! ('contains match')
  compareFn: (value: any, searchQuery: string) => value == searchQuery              // Example for an 'exact match'
  compareFn: (value: any, searchQuery: string) => value != searchQuery              // Example for a 'not match'
});

Code

// Get a DataStore Instance
this.customerStore = StoreManager.get("customer");

this.customerStore.fromObservable(getDataFromObservable()).then(() => {
  // DataStore is filled with data from the given source
});

// Create a DataStoreView from customerStore
this.customerView = new DataStoreView(this.customerStore);
this.customerView.addSelection("fromKoeln", {city: "Köln"});

// Query an object
this.customerView.query({city: "Köln"}).subscribe(result => {
    console.log("Result", result);
});

// Query an object as a StoredQuery
this.customerView.queryAsObservable({city: "Köln"}).subscribe(result => {
    console.log("Result", result);
});
<!-- 
Access an Presentation from a DataStoreView Synchronously (as an Array) 
-->
{{((customerView | selection:'fromKoeln':false)[0].zipcode}}

<!-- 
Access an Presentation from a DataStoreView Asynchronously (as an Observable)
This Asynchronously method is preferred if the DataStoreView could contain a large DataStore
-->
{{((customerView | selection:'fromKoeln') | async)[0].zipcode}}

If you find any bugs or have a feature request, please open an issue on github!

The npm package download data comes from npm's download counts api and package details come from npms.io.