Having tough time with the Observer pattern

It does mean a bit more overall code yes, but you’re repeating smaller simpler repeatable blocks.

for me I usually have same <app-header> <app-page-layout> <app-footer> approach but do it at component level the app component itself really just handles routing, storage and state it doesn’t really have much of a template, maybe a slideout menu at most…

I split the data services into groups.

The first is any data that can be preloaded happens in

data.service -> init.service -> app.component

and is loaded into the Webviews local IndexDB. The init.service can be accompanied with a state.service which can store bits of application state and global Observables and Subjects. Later if state starts getting complicated you can easily retrofit a Redux/flux pattern …but always later.

In Ionic/Angular there’s the idea of a “Page” level component which is done in the CLI tool and it’s always best to use the CLI to generate your files and folders. So from there it’s

data.service -> mypage.page -> ...sub.components

or

data.service -> mypage_got_a_bit_complicated.service -> mypage.page -> ...sub.components

When designing the components of a view it can get complicated and usually a time comes where you want to reuse one of your components in a different project.

Try to keep them small and functional just data goes in something gets displayed, the user does something or the data is modified programmatically @Input and the result outputs
@Output @EventEmitter @Subject, back to the page level component, it’s all tied together there or in whatever code behind pattern you prefer.

this is just another approach to consider, it’s worked for me but there’s always a better or different pattern out there that works for you.

I’d argue this is a matter of opinion. Mine is that one is better off writing the files from scratch, especially when starting out, in order to familiarize oneself with what is really going on. I also think that the lazy-loading foisted upon people by default with the generators causes confusion that greatly outweighs their value, again especially exacerbated when one is new to the framework in general.

It sure is only a humble opinion of one and the generators themselves are only really the opinion of their creators. There’s plenty of reasons you might not want to roll your own depending on the scale and complexity of the application.

But in general I’d argue back that if you’re not familiar with a framework or application architecture, Ionic and it’s supported frameworks have pretty good generators that will scaffold applications in a standardized way that will always work well, are easy to understand and follow the upgrade path. Great for learning and still good for most use cases of small to medium applications.

To lazy load or not is a whole other subject of debate, but yeah it is annoying and confusing to setup, having the generator deal with it saves a lot of time and rolling out of lazy loading strategies is easy, agree it would be nice to have the option to disable it though.

You’re totally right that generators aren’t a silver bullet for every application, but they are a great place to start from.

@rapropos, I could make my multilevel test app work with the information you provided, thank you ! I only used BehaviorSubject, watch, peek & setters.

On the other hand:

  • I had to console.log every step to find multiple executions caused here and there
  • At the ContentService I had to define _content$ also as BehaviorSubject for being used at upper levels. I have/had problems with multiple processing at this level.
  • I had to put some if-then-else to check the initial “undefined” case where the business logic needs to access contents.

I have two questions regarding your code in constructor with “untilDestroyed”:

  • In many examples subscription code is ngOnInit. I can see that putting the subscriptions into the constructor has no problems - and I think - provided that “undefined” case is handled. And this leaves ngOnInit for @Input related variables only. Can I say that?
  • I’ve been extensively using the async pipe in templates. As far as I know these are also subscriptions. Will they collide with the code in the constructor? I think I see some leftover multiple executions because of that. Should I drop the pipe or is there any other possibilities?

And one feeling I have to express: I would never think a BehaviorSubject to be a main data structure. For me a data structure is a record, array etc. Having the real data burried in BS is just not natural for me.

And another one: I learned that signal (event) flow is major part of the design. While designing reusable components one has also think this aspect beforehand. And when writing the word “subscribe” I need to think twice. If you do destructive data manipulation (e.g. replacing content) you may fire multiple times.

That brings me to @digital-pollution’s suggestion: Take the data structure out of the subscription at a higher level and pass the data towards lower levels. If event flow is working, the data will be updated.

Unfortunately I could not upload the files to stackblitz as they are, that environment is also new to me. After so much effort it could be useful for other newbies. And of course: I might have other major misunderstandings that you all might find at a glance.

Hi there. I like to chip in. I think ideally/if possible you don’t subscribe (and unsubscribe) explicitly using code, but let the async pipe do it for you - if you can avoid it. You just need to use the rxjs magic transform, join etc the datastream into what you need for the user.

In an ngIf to create a local variable or a ngFor to do the loop are the possible ways to get data from observable streams in the template.

I don’t think it is a data structure. It is the road to transport and notify content of data structures.

You should write components to be fairly isolated (pure is the right word?) for testability and reusability reasons, making them agnostic about when what will happen. But when it happens it should know what to do for the user.

Angular should do the trick on change detection (and if you create the state mgt pretty pure and immutable, you can change the detection strategy to Push, so the performance gets better).

The one thing you may bump into as well, if you need to observe one data stream in multiple objects, then the usage of asObservable() comes in handy - looking at some of the posts before.

Afterall, you can only subscribe once to an Observable instance. (right?)

Have a good one!

Tom

I wouldn’t stress overly much on the distinction between setting up subscriptions in constructors versus ngOnInit, especially in services that typically aren’t reaped until the app is.

Another religious argument, and seeing as how @Tommertom has already covered the pro-pipe position, I’ll take the opposite. I tried to love AsyncPipe. I really did. In the end, after about a month of wrestling with it, I sacked it, and here’s why.

First, as I’ve already mentioned, is the philosophical objection: I don’t like mixing logic and presentation: I find it hampers readability. Sometimes things that didn’t used to be asynchronous become so. Sometimes it goes the other way. Templates shouldn’t be bothered with this.

Secondly is the practical matter of “how do I debug this stuff when things go wrong?”. I’ll give you a concrete example that actually happened to me. You have an Observable that is backed by an HTTP request, subscribed to by an AsyncPipe in a template. You decide to add an app-wide network “busy-ness” indicator by writing an HttpInterceptor that keeps track of a reference count of open connections. Now you inadvertently are subscribing to the same HTTP request twice, and that’s (a) a bad idea and (b) extremely hard to debug because you can’t set breakpoints in the AsyncPipe’s subscriptions. It took me taking out the AsyncPipe before I could see the double subscription. I spent several hours staring at server logs wondering why there were being multiple duplicate requests to the same endpoint.

So I prefer to manage subscriptions strictly in the controller, and pretend the AsyncPipe doesn’t exist. The way I see it, the biggest value of AsyncPipe is that it manages the unsubscriptions for you, and since ngneat/until-destoy does that job in what I would consider an even less obtrusive manner than AsyncPipe, I use it instead.

1 Like

(Without taking my lightsaber out)

I cannot say Angular is promoting an MVC architecture or similar. As long as ngIf and ngFor are there, it is designed in the contrary.

If your component is small, where you just get the input data and present it in loop, async pipe seems to be easy to code.

On the other hand, if you do some data manipulation and want to debug, it can easily become a hell. If you look at my first post, you will see that I was there. On the console I saw 200+ deep calls to to click and watch for variable values to find out where it is coming from.

I coded in 30+ languages and this environment is not the best as far as debugging is concerned - it is not even on live environment. I can compare it with my hardware debugging experiences, where some noise is coming from an unknown source and causing HW interrupts (it turned out to be a unshielded fan speed controller in the room).

So, what I learned is: I will either use pipe or code in component, not both. I have been doing it wherever I see fit :confused:

1 Like

On Subjects / BehaviourSubjects, I’m not recommending the data or application state sits inside the Subjects themselves.

You’ll still want to hold any in memory data and state at least in a global variable, or with whatever state management pattern you prefer. This is simply because there will likely be many times you want to access state programmatically without the need for managing a subscription.

Subjects are just broadcasters to notify other components of state / data changes. They could / should return new state / data, but maybe shouldn’t be used as the source of truth, just the messenger.

edit: imo forget the async pipe, do async await in the class only.

@digital-pollution I might have gotten this wrong, but:

We have been talking on a GlobalsService all along, where I’ve been keeping app-wide data structures and current state. And with get/set/watch (as @rapropos suggested) we can access every possible route. If a BehaviorSubject has no subscription it will not fire anything anyway.

On another note, coming from an era where we had to count every byte (and if possibly encode in bits - which I currently do when I’m writing firmware for microprocessors) and every processor cycle, I’m always curious about the resources I would use. It will define my coding style for the future. Probably this will translate to battery and/or GSM network use for PWA case.

I’m not eased with the idea, but I saw I can work with only BehaviorSubjects (with correct initialization and checks). It keeps the data inside it and I can access it. One of the data structure I keep globally and during the whole lifetime of execution is an array of objects. Wouldn’t that mean bad practice if that data is duplicated like this:

  private _appModules: IModule[];
  private _appModules$ = new BehaviorSubject<IModule[] | undefined>(undefined);

I used BS before (in my one and only app) several years ago, but it was firing a “LanguageChanged” in that multilingual app. Simple 2 char string…

Curious, what do you all think?

I kinda only worry about bandwidth and cloud resource usages (Firebase in my case). But then again, depends on the type of project. If you run Tensorflow.js, you might need to be worried? Or any WASM indefintely loop thing?

hint: go WASM if you like assembly :slight_smile:

Not sure if I get you, but imo you shouldn;t be worried about dataduplication in _appModules$ if the service provider uses data from _appModule to emit (appModules$.next). In fact, I think under the hood, if the object you send is an array, it will emit a pointer to the array, meaning if the consumer manipulates the array, you change the data in the service provider - which I forgot about if it is like this, because it is really BAD practice.

In Angular you should be worried if you provide a service provider multiple times (or not in root) - as that will duplicate and confuse a lot.

Regards

Tom

@Tommertom

Tom, thank you for the insight.

All in a good time :slight_smile: ABC first…

OK, lets check if I understood:

  1. If it only sends a pointer, there is no data duplication. This is good.
  2. When a subscriber in a component does the following:
this.dataSrv.getModules$().subscribe( modules => { ... } )

the modules variable should be a copy of the actual data, not a pointer. So you can manipulate that data as much as you can.
3) If you want to change the data, you use a setter:

this.dataSrv.getModules$().subscribe( modules => {
  modules.forEach( m => {
    this.dataSrv.setModuleTextById( m.id, m.text + '-I changed this')
  }
} )

You cannot directly change the private data in a service through a pointer, without getters/setters. If you define it R/W public, you can, and that would be what you call BAD.

You meant this?

Hmm, I’m not sure if I understand. Do you mean this (comes from generator - yeah I use it :slight_smile: )?

@Injectable({
  providedIn: 'root',
})

I see Angular changed a lot from v5, but defining services as singletons is common practice AFAIK. On the other hand, I’m trying to write a modular application. There might be special services for some modules and I was planning to put them inside that module’s package (not tried any yet). Is this a bad practice?

I think you should push yourself to redux type of thinking. Meaning, amongst other things, you should never should want to manipulate the data received through observables - only do a READ on them.

Irrespective if they are pointers, or clones. Just don’t assume any of that, also to stay consistent.

If you seek to change the data (any typed object), you should clone it locally in the component, or use const or let to get parts of it in a local instance (for string, number types ). Typical cloning patterns I have seen are Object.assign, using lodash (library?) and I use JSON.parse(JSON.stringify( (not cloning types, functions, and my objects can be deep - a doubtful practice too btw).

Spreading operators are actually the best way to go, I gues: [... ] or {... }.

Obviously, indeed you need to tell the service that things should change, but then you should be aware that the service provider will emit a new value. Meaning, a new state is achieved.

As long as you provide in root, meaning also app.module.ts refers to it, then you are good. All providers wil be a singleton then.

@Tommertom
Tom, that’s a lot to take in… I shall shut up and learn (a lot) :confused:
I’ve been using Object.assign elsewhere, but never thought I should use it for this. I must review my design patterns and learn more on JS/TS…
Thank you for the insight, that was very helpful…

1 Like

This is a very basic example of a service I might inject into the app.component if I think I don’t need state management. You truely do not always need to add the complication of state management to a small app, but if you’re doing something complex then consider jumping straight to something flux based for angular like https://www.ngxs.io/

item.service.ts

@Injectable()
export class ItemService {
  private _items: Item[];
  public sub: BehaviourSubject = new BehaviourSubject([]); 
 
  constructor(
    private storage: Storage
  ) {
      // and or data from an api
      this.storage.get('Items').then((items) => { this.loadItems(items) });
  }

  public loadItems(newItems: Item[]) {
      // update global _items and alert any subscribers
      this.items = newItems;
      this.sub.next({updatedItems: this.items})
  }

 public saveItems() {
      // and or posting back to an api
      this.storage.set('Items', this.items);
  }

  public add(item: Item):void {
    this._items.push(item);
  }

  public get items() {
    return this._items;
  }

  public set items(items: Item[]) {
    this._items = items;
  }
}

This is one of those things that seems like a neat idea at first, but it has (at least) one massive problem. You may think you hate JavaScript, but I doubt your hate for it runs half as deep as mine. That being said, one great thing about it (and loosey-goosey languages in general) is that it’s easy to mock things out for testing.

Just make a ThingyServiceMock that implements enough of ThingyService’s public shape, swap out ThingyService for {provide: ThingyService, useClass: ThingyServiceMock} in whereever it’s provided, and off you go.

Except if ThingyService is decorated with providedIn: 'root’. Then it’s impossible, full stop.

Ionic Native tried this briefly, and reverted it, thankfully.

So my opinion is that providedIn in @Injectable is a misfeature. Services should not be declaring where they are instantiated - that decision should rightly lie above them in the org chart. Declare them in the AppModule’s providers stanza instead.

@Tommertom @rapropos @digital-pollution

Dear friends,

It took a couple of days, but I managed a complete re-structure of my code. It is now working flowless with many additions.

This discussion has been very informative and very productive. Thank you !

I wish you all a healthy, wealthy and most happy 2021.
Long live and prosper…
Bülent Özden

Thx, same to you and happy coding!