#@Input() & @Output() use outside of templates?

166 messages · Page 1 of 1 (latest)

fierce smelt
#

So I keep seeing examples like these in the official Angular docs, that have to do with parent and child components, seemingly within the same template…

Two Way Binding
AppComponent
app.component.ts

fontsize: number = 16;

app.component.html

<app-sizer [(size)]="fontSizePx"></app-sizer>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>

SizerComponent
sizer.component.ts

export class SizerComponent {
  @Input()  size!: number | string;
  @Output() sizeChange = new EventEmitter<number>();

  adjust(value: number) {
    this.size = value;
    this.sizeChange.emit(this.size);
  }
}

sizer.component.html

  <button type="button" (click)="adjust(-1)" title="smaller">-</button>
  <button type="button" (click)="adjust(1)" title="bigger">+</button>
  <span>FontSize: {{size}}px</span>

Which all starts off with even worse in earlier tutorials, setting this precedent off the bat…

Consider the following hierarchy:

<parent-component>
  <child-component></child-component>
</parent-component>

AppComponent
app.component.ts

export class AppComponent {
  items = ['Television', 'Bowling Ball', 'Teddy Bear', 'Toaster'];

  addItem(newItem: string) {
    this.items.push(newItem);
  }
}

app.component.html

<app-item-detail [item]="items[0]"></app-item-detail>
<app-item-output (newItemEvent)="addItem($event)"></app-item-output>
<ng-template *ngIf=“items.length > 1”>
  <p>All Items</p>
  <ul>
    <li *ngFor="let item of items">{{item}}</li>
  </ul>
</ng-template>

ItemDetailComponent
item-detail.component.ts

export class ItemDetailComponent {
  @Input() item: string = “”;
}

item-detail.component.html

<p>Today's item: {{item}}</p>

ItemOutputComponent
item-output.component.ts

export class ItemOutputComponent {
  @Output() newItemEvent = new EventEmitter<string>();

  addNewItem(value: string) {
    this.newItemEvent.emit(value);
  }
}

item-output.component.html

<p>Add another item to list for later</p>
<label for="item-input">Additional item:</label>
<input type="text" id="item-input" #newItem>
<button type="button" (click)="addNewItem(newItem.value)">Add to list</button>

Here’s my problem however
If you refer back to the “consider the following hierarchy part”, my app looks like that, but at the same time also doesn’t actually look like that at all…

My app, to be precise, looks like this

<body>
  <app-component>
    <router-outlet></router-outlet>
  </app-component>
</body>

Paired with a simple Router, and basic paths configured like this

const routes: Routes = [{
  path: 'home',
  component: HomeScreenComponent
}, {
  path: 'feature',
  component: FeatureScreenComponent
}];

So I DO in fact actually need to share data Parent -> Child, and some times vice versa, yet the only way Angular documents doing this is through template syntax and under the presumption that both components appear in the same template, or that at least the child component explicitly appears within the parent template

None of my components are using that pattern, much less even necessarily appearing in ANY component.html files even ONCE, as their dom elements are being written in by the Router, and not by me

❓ So how do I establish these sort of bindings between my AppComponent and ChildComponents when I’m not in control of writing the markup and it’s dynamic like this? Can it even be done?

naive reef
#

I think that's what the docs use services for, but i believe that's not what you mean.

#

I don't think you can use inputs, if you cant set the input where the component is used, which if i understand correctly is what you are asking.

#

A component that's rendered by the router probably shouldnt have an input.

#

Data to be shared from app component to a routed component is more about app state than it is about input data.

fierce smelt
#

Trying to resolve the binding horrors I brought up earlier… where the AppComponent and child components need access to the same Object for us in their individual templates, cross component

So I took a deep deep dive into the whole input/output paradigm, only to discover that it didn’t mention this super simple router pattern at ALL 🥺

and you already know the luck I’ve been having with observable subscriptions via services, and messing with change detection 😞

naive reef
#

Yeah, as i said last time, i believe the issue is with your pattern, no offence ofcourse! I would try and solve that rather then keep trying to fight your way around that

#

Dont try and share the same object instance, angular doesnt typically like that as you noticed.

#

Well it does, but it can get out of sync if u change the object in one place.

fierce smelt
#

But what should a proper pattern look like??

Do I just need to clear out my <body> entirely of everything but the <router-outlet>, and then mention <app-component> as the first element and wrapper to everything/anything within my child components templates?

naive reef
#

With pattern, im refering to An earlier discussion about the subscriptions.

#

Nothing wrong with the pattern u mention in this thread imo.

#

Im referring to the getters, and the value u pull from behavior subjects, and the fact you then send it to a mediator , and then pull it out in the components again.

fierce smelt
#

Yeah, so I figured if I dropped ALL that, just simply used a service to fetch the data ONCE, then I could use that one simple object in every template, via input/output or something somehow

#

Rather than subscribing to updated copies, or dealing with getters/setters at all

naive reef
#

Thats definetly not what i Mean.

fierce smelt
#

But reading through these input/output docs, the whole time I’ve just been like… “uh… okay, so when you going to mention router outlets? 😰”

naive reef
#

Let me get my laptop to try and show what i mean.

naive reef
#

Firstly

#

U use mediator because you want a push based pattern no?

#

rxjs is already push based tho.

#

(entirly out of scope here, but I do recmmend looking into how ngrx solves these things)

fierce smelt
#

Push pattern isn’t important to me, I only used it to try to debug stuff being out of sync, to see if I could manually trigger refreshes

naive reef
#

Push based is important.

#

Hence why angular has OnPush.

#

Which u lose entirly if u use plain objects.

fierce smelt
#

I simply want to make an Ajax call, get back some JSON, parse it into a simple key:value property vanilla JS Object, and then give all my components access to that object for rendering it’s values in their templates

#

👌 this close to declaring window.data = … 😅

naive reef
#
class MyService {

  data$ = this.http.get(...).pipe(shareReplay(1));
  constructor(private http: HttpClient) {}

}

class MyComponent1 {
  data$ = this.svc.data$;
  constructor(private svc: MyService) {}
}

class MyComponent2 {
  data$ = this.svc.data$;
  constructor(private svc: MyService) {}
}

Both HTML then use

<ng-container *ngIf="data$ | async as data">
  {{data | json}}
</ng-container>
#

Thats very simplified, but that avoids any subscription, ensures u can use it in multiple components while only ever doing one http call.

#

It also ensures its push based and ensures angular runs CD when needed.

fierce smelt
#

I started there, but found that only one component ever got anything back from the service subscription

naive reef
#

Then u didnt use shareReplay(1) ?

fierce smelt
#

I did not…

naive reef
#

shareReplay ensures it shares the same subscription (doing one http call at a time even if subscribed to multiple times) and replays the last known value to any future subscriber.

naive reef
#

The last bit is what u need to ensure u also get the value if the component is rendered AFTER the http call is done

fierce smelt
#

I’ll let you know how this attempt goes…

naive reef
#

Also using the async pipe does the CD magic.

#

Now lets say u need to update the value ... Then it gets more complicated.

#

This is were ngrx shines, which is a state management solution

#

Without it, u need to so some more magic. But the important part is u communicate back to the component using an observable, eg:

fierce smelt
#

I’ll definitely need to be able to do everything again eventually…

naive reef
#

What do u mean with doing again?

fierce smelt
#

If I haven’t explained the app yet, it’s a native iOS app (wrapped in Cordova) that captures checks

Screen 1:
Start screen, with nothing but a single text input and submit button. This is for entering a PIN number, which pulls up all the account details

Screen 2:
Displays the account details for review/verification, has a “continue” button

Screen 3: Captured front of check

Screen 4: Captures back of check

Screen 5: Shows review/preview of captured images for confirmation, and has an “upload” button

On upload success, they get a confirmation pop up, and then on close of that pop up… back to Home Screen for another PIN, rinse and repeat

naive reef
#
class MyService {
  private dataSubject = new BehaviorSubject(null);
  data$ = this.dataSubject.asObservable();
  constructor(private http: HttpClient) {}
  
  getData() {
    this.http.get(...).subscribe(value => this.dataSubject.next(value));

    return this.data$;
  }
  update() {
    const value = dataSubject.value;
    const updatedValue = {...value, newProp: 'value' };
    dataSubject.next(updatedValue);
  }

}
#

The idea here is to use a backing behavior or replay subject.

#

Dont expose it, we expose an observable instead, so consumer can only read.

#

U also did this, getting the value out of it.

#

I tend to avoid that, because I use ngrx which does that under the hood, which keeps things a bit nicer.

#

But the important bit is to communicate back with observables.

#

Which u also had partly, with your mediator.

#

But the getters should have no use and break the reactivity pattern once u use that to consume the value in ur app.

#

Consuming this in a component is the same, with the only difference now that u need to call getData() somewhere first, to ensure it loads the data initially.

#

Use async pipe, let angular run CD for u.

fierce smelt
#

So then after a successful capture and upload session, once the user is returned to the Home Screen to help the next/different customer, how will I make my Ajax call/observable fetch a new object, instead of just replaying the last session’s value?

naive reef
#

call getData again

#

with params as u need them.

#

Could even use a clear() first if needed.

#

that does dataSubject.next(null)

#

and ensures the entire app gets notified of that change

#

or atleast all parts that use data$.

#

Honestly, this is 100x cleaner with ngrx. But its yet another tool to learn if u dont use it already.

fierce smelt
#

Okay then… round number (lost count now), ready to go again…

naive reef
#

Happy to try and help u trough it when u are stuck tho.

fierce smelt
#

🙏

naive reef
#

As mentioned last time, if u have time ... Make a stackblitz.

#

The pattern u have has nothing to do with cordova.

#

So would be more than happy to help rework parts where I can to help u understand what's going wrong.

#

But honestly, drop the idea of getters.

#

I like them, I use them, but I never use them for this.

#

its not that its impossible to make it work.

#

It just complicates things alot

#

(also this entire response has nothing to do with your question, people might be confused haha. I just appear to remember your previous question 😂)

#

or well, it does have something to do with it ... but u get what I mean 😄

fierce smelt
#

Yes, that they will not 😂

#

You did provide information in direct response to the original question though, that in/outputs through a router outlet don’t work

naive reef
#

Feel free to ping me if u have gotten any further (or havent), I tend to not like to throw half baked solutions at people and have them suffer with it, but I also feel like I cant do much without getting my hands on it.

#

Did you use OnPush somewhere?

#

I forgot about that

fierce smelt
#

I tried it in AppComponent, since it was the AccountComponent in specific doing the fetching of the data

I did manage to make some good use of it though I think in another more or less insignificant component that I believe is working as intended, and that I’m going to keep

#

There’s a version number in the bottom right hand of the screen, and non end users can change the environment during run time. Got tired of digging into app info to know which environment I was in before starting a test run, so I made it different colors based on which environment we’re in

It was updating on environment change, but not as smoothly/consistently as I’d of liked, so I made it onPush as a trial for my learning experience. Went well, and changes immediately now.

naive reef
#

This shows how one part of the app updates, while another doesnt.

#

This has nothing to do with observables tho, but based on what I understand I have a feeling your experiencing comparable issues.

naive reef
#

If anything, it should be less (if the code isnt doing what it should)

fierce smelt
#

Well I mixed in a markForCheck whenever the environment changes

naive reef
#

Ah, yeah. That works.

#

But I dont see how that would be more reliable then without onpush 🙈

fierce smelt
#

I know, right??

naive reef
#

its better, for sure. But without OnPush it should still update.

#

Any chance any of its parent components had onpush?

#

coz THAT could explain it/

fierce smelt
#

Nope

naive reef
#

What about AppComponent?

fierce smelt
#

They both did at one time, after I concluded my test run with this little component was a success, and tried to use the same pattern to fix my bigger sync issues

But it’s worked better and more or less the same since before I made AppComponent onPush, as well as since I changed AppComponent back

naive reef
#

If u make AppComponent OnPush, u will break things.

#

Do u know what OnPush means?

#

Or what the effect of using it is ?

fierce smelt
#

Yes, it effectively cancels all the auto changes, and makes you need to trigger (push) them

naive reef
#

For that component, and all components below it in the DOM tree yes

fierce smelt
#

I had a feeling it wasn’t a healthy choice for AppComponent, but at the same time the app is so small, and I was desperate

naive reef
#

Well it doesnt cancel it

#

I think its not a bad choice tho

#

But u cant just toggle it

#

itll break things for sure

#

OnPush, in short, tells to angular u want to disconnect this component and all of its children from running CD every single time SOMETHING happens. Instead, u tell it to only ever run CD when u update any of its inputs.

#

AppComponent has no inputs.

#

So u are telling it, never rerender.

#

(unless I tell u to using markForCheck etc)

fierce smelt
#

Which is what I had implemented

naive reef
#

Yeah which also worked if I understood correctly

#

Also markForCheck only marks that branch of the DOM tree, not EVERYTHING

fierce smelt
#

and knowing that it would mean I’d have to do that to everything, everywhere from now on, was what I recognized probably wasn’t the healthiest choice

#

But again, small app and desperation… didn’t work out though, so I rolled it back

naive reef
#

Yeah I would always start applying onpush bottom to top

#

not top to bottom 😂

#

But the best performance is probably when u use onpush everywhere and ensure you only tell angular to rerender when u know it should.

#

But thats also probably the hardest.

#

Its easier to rely on rerendering it every single time.

#

Are u saying that, when no single component uses OnPush, u still have the issue?

fierce smelt
#

Yes

naive reef
#

That makes no sense tho.

#

Would be very curious to see that happening.

fierce smelt
#

Each component gets the latest version of the object out of their subscription to the mediator service, and they run this.object = object

Inspecting from the console, component.object is accurate in all of them

Yet across the various template components where you might see something like {{ object.property }}, only SOME of the components rendered the new data, and some stayed stale

naive reef
#

Which makes no sense if none of those use OnPush, or are part of an ngFor using trackBy (not sure about the latter tho)

#

I am 100% sure that using the async pipe like I showed u should solve it.

#

But I am also 99% sure that what you say you are doing, and what I showed, should work if u never have any OnPush.

#

So I am curious what other beast is making u hit this behavior.

fierce smelt
#

I was wondering something about ngFor reading the documentation

Can I use it outside of an array or object? Like what if I’m not interested in looping, but just kinda want to strengthen my binding by associating something/anything with an ngFor

Like the examples read

let hero of heroes

and then you use {{ hero.name }} where there are multiple heroes

Can you just do something like

let object

?

naive reef
#

what would object be ?

fierce smelt
#

A single hero?

naive reef
#

I am not sure what u mean haha

#

sorry 😄

fierce smelt
#

Like if in component we had

const multipleHeroes: Array<Hero> = […];
const justOneHero: Hero = …;

Then could you do

let justOneHero

no using “of”, and then further down in the template do

{{ justOneHero.name }}
naive reef
#

I am not sure why you want that

#

But no u cant, as u already have a justOneHero defined.

#

{{ justOneHero.name }} works in a type safe manner already without the let.

fierce smelt
#

I just thought it might strengthen my binding… never invested in trying though, as I assumed it wouldn’t work… just hadn’t found anything yet to assure me it wouldn’t

naive reef
#

U can do something that looks like it but yeah not sure what it would strenghten.

#

And it would involve ng-template for no reason.

fierce smelt
#

Oh well, going with this replay route for now

naive reef
#

Hang on tho.

#

Based on your last info, I would argue you should be able to keep things as is if u leverage async.

#

I would still consider giving it a look tho.

#

As the getter realy is dangerous imo.

#

But if u dont use onpush, it should work.

#

But ye, its hard to tell from here.

fierce smelt
#

Still need to change my components though and update the templates

Because right now, I have functionalService get the remote data, then call next() with that data on mediatorService

All the components pull in the mediator service, not the functional one

And then, they pull the data OUT of the mediator and do this.object = data, so the template can use {{ data.value }}

Based on what you said, all the components should pull in the functional service, and instead of storing objects should store observables, as returned directly by the functional service

And then in the template I need to do

data$ | async

or what not

naive reef
#

Can u show me ur mediator service

fierce smelt
#

after adding a replay pipe of course

naive reef
#

Like does it use some library?

#

Or is it pure rxjs?

fierce smelt
#

My work has Discord blocked, using my iPad right now

I’ll see if I can take a photo here in a bit

#

and no, no library

Just RXJS

naive reef
#

Dont ur components do

#
this.mediator.data.subscribe(data => this.data = data)
fierce smelt
#

Yes, pretty much

naive reef
#

Start with trying:

this.data$ = this.mediator.data;
#

and data$ | async

fierce smelt
#

Got it

naive reef
#

That should work as well.