CAUTION: This post is really just me thinking out loud on state management concepts in Angular 7. Consider this nothing but a work in progress from the mind of someone who barely understands what they are doing.

Earlier this week, I shared my current mental model for state management and the separation of concerns in an Angular application . After banging my head against Redux, I realized that what I wanted was an abstraction that completely hid Redux from me; an abstraction that would allow me to choose Redux later on, should I actually need it. I called this abstraction the "Runtime". This is very similar to the "Workflow" abstraction that I discussed 2-years ago (and very similar to the Facade and Sandbox abstractions discussed in my previous posts). Today, I wanted to start experimenting with some concrete code to help exercise the abstraction and see if it is something that I actually want to use. As a test harness, I created a little app for "Santa's Christmas List" that helps track "nice" and "naughty" citizens.

Run this demo in my javascript Demos project on GitHub .

View this code in my JavaScript Demos project on GitHub .

Because the goal of the Runtime abstraction was to hide the very notion of state management from the View, let's start with the App Component. The App component presents a list of Nice and Naughty people as well as a form that will allow the user to add a new person to the currently-selected list.

As an initial pass on this experiment, all of the values coming out of the Runtime abstraction are RxJS Observable streams. These streams emits values when the internal state of the Runtime changes. And, the internal state of the Runtime can be mutated using various public methods.

// Import the core angular services. import { Component } from "@angular/core"; import { Observable } from "rxjs"; // Import the application components and services. import { ListType } from "./santa.runtime"; import { Person } from "./santa.runtime"; import { SantaRuntime } from "./santa.runtime"; // ----------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------- // @Component({ selector: "my-app", styleUrls: [ "./app.component.less" ], templateUrl: "./app.component.htm" }) export class AppComponent { public intake: { name: string; }; public selectedListType: Observable<ListType>; public people: Observable<Person[]>; public niceCount: Observable<number>; public naughtyCount: Observable<number>; private santaRuntime: SantaRuntime; // I initialize the app component. constructor( santaRuntime: SantaRuntime ) { this.santaRuntime = santaRuntime; // The intake form only operates on local state. There's no need for this to be // a concern of the runtime (though, if this demo were more complicated, it is // possible that error-messages could be controlled by the runtime). this.intake = { name: "" }; // Hook up the various runtime streams. this.selectedListType = this.santaRuntime.getSelectedListType(); this.people = this.santaRuntime.getPeople(); this.niceCount = this.santaRuntime.getNiceCount(); this.naughtyCount = this.santaRuntime.getNaughtyCount(); // --- // PUBLIC METHODS. // --- // I get called once after the inputs have been bound for the first time. public ngOnInit() : void { var hash = window.location.hash.slice( 1 ).toLowerCase(); // If the window location (a VIEW CONCERN) is indicating a list selection, then // let's update the runtime to match the list selection. if ( ( hash === "nice" ) || ( hash === "naughty" ) ) { this.santaRuntime.selectList( hash ); // I process the new person intake form for Santa's list. public processIntake() : void { if ( ! ) { return; // NOTE: We don't have to pass in a list-type because the currently selected list // is already part of the runtime internal state. As such, we only have to pass // in the name of the person and the runtime will take care of the rest. this.santaRuntime.addPerson( ); = ""; // I remove the given person from Santa's lists. public removePerson( person: any ) : void { this.santaRuntime.removePerson( ); // I show the given list of people. public showList( list: ListType ) : void { // Update the location hash (a VIEW CONCERN) so that we start on the selected // list if the browser is refreshed. window.location.hash = list; this.santaRuntime.selectList( list );

As you can see, the App Component injects the SantaRuntime and then immediately binds to various state-streams, like SelectedListType and People. You can also see that it declares a local object - instead of a stream binding - for the person intake form. There's no need for this Form information to be maintained in the Runtime state - it is a view concern (in this application).

The nice thing about the Runtime abstraction is that it keeps the App component super simple. There's almost no logic here other than the logic that tries to sync the Browser URL and the selected list type.

The downside is that the component property names feel super janky to me. They read as static values; but, in actuality, they are streams. Really, I should be appending a suffix like "Stream" (as in "PeopleStream") to these property names. But, I figured that, for a first-pass, I would try to keep things as simple as possible.

ASIDE: A lot of people seem to append the "$" suffix to indicate an RxJS "stream". I am not sure where this came from or why that was chosen. As such, I don't want to copy this approach blindly. It also smacks of "Hungarian Notation", which is something we struggled with back in the jQuery days .

Naming aside, I also feel like the use of streams here makes my App Component view syntax look rather janky:

<h2> Santa's Christmas List </h2>

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

本文标题:Experimenting With The Runtime Abstraction For State Management In Angular 7.0.3

技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(10)