I'm learning Angular from the very beginning, and coming from React, I can't find a way to manage a simple state in Angular. It's a simple todo app, and my strategy is to create a Todos array in todo.service.ts
#Injectable()
export class TodoService {
todos: Todo[] = [
{ id: 0, title: 'Cooking', completed: false }
]
nextId: number = 1;
constructor() { }
public getTodos(): Todo[] {
return this.todos;
}
public addTodo(title: string): any {
this.newTodo = new Todo({ id: this.nextId, title, completed: false })
this.todos.push(this.newTodo);
this.newTodo = new Todo();
this.nextId++
}
public deleteTodo(chosenTodo: Todo): any {
this.todos = this.todos.filter(todo => todo.id !== chosenTodo.id)
}
}
Now on the ListingComponent, I want to get all Todos, and then pass it down to ListingItemComponent, as such
todos: Todo[] = []
todoTitle: string = '';
constructor(private todoService: TodoService) { }
ngOnInit(): void {
this.todos = this.todoService.getTodos();
this.todoTitle = 'Do Something';
}
addTodo() {
this.todoService.addTodo(this.todoTitle);
}
<div *ngFor="let todo of todos">
<todo-listing-item [todo]="todo"></todo-listing-item>
</div>
/* This just adds a todo with title 'Do Something' for testing */
<button (click)="addTodo()">Add Todo</button>
Finally in ListingItemComponent
#Input() todo: Todo
constructor(private todoService: TodoService) { }
ngOnInit(): void {
}
deleteTodo(): void {
this.todoService.deleteTodo(this.todo)
}
<h1>{{todo.title}}</h1>
<button (click)="deleteTodo()">Delete Todo</button>
Now the component has no error, and I made sure to declare everything, but it's not working. Clicking the button won't change anything. After digging in, I changed the ListingComponent into "let todo of todoService.getTodos()", and remove the todos in the ts file along with the ngOnInit, and somehow it works. My guessing is that the ListingComponent doesn't know that the todos array has changed, and so it's not rerendering?
So if I was to initialize the todos in the ngOnInit like I did before, do I need some other life-cycle to listen to the change of todos?
If many components need the todos array, should I write ngOnInit(): void {
this.todos = this.todoService.getTodos();} on every single one of them? And if one component changes their todos, can other components listen and rerender as well?
Thank you for reading. In React I would just create a global state, along with the functions, and pass it as props down to every child. But I can't wrap my head around managing state in Angular. Is it because I'm thinking in React, while I should not?
Let me first say that I'm a huge fan of NGRX and the pattern, and I suggest you make this part of your learning path. However, if you're looking for simple state management than a simple Observable will do the trick.
I created a stack overflow to illustrate the technique using the code you provided:
https://stackblitz.com/edit/angular-ivy-kfw1sd. In this demonstration, I'm leveraging BehaviorSubject which is a special type of observable.
In closing, do your best to avoid .subscribe() and use the async pipe instead.
Hope this helps,
Isaac
If you want to have a simple global state, you can start using Shared Services, obviously it will be only for learning purposes, because if you want to use in a real project, you can use Shared Services, but also, you would need Facade or other Design Patterns.
Main advantage using Facade is that you can migrate to NgRx easier.
I have an example using API + State + Base Architecture: https://github.com/cmandamiento/angular-architecture-base
Feel free to reach me out.
Indeed you do not always need to add another Library to manage state in Angular. Angular comes with RxJS out of the box...
All the well known state management libraries use RxJS/BehaviorSubject internally. This article shows how to write a simple DIY state management Class which can be extended by Angular services.
Simple yet powerful state management in Angular with RxJS
This solution is also based on RxJS/BehaviorSubject.
Maybe it can be useful for you. The code example in the article is a Todo App :)
Disclaimer: I'm the author of the library
ng-simple-state
Simple state management in Angular with only Services and RxJS
See the stackblitz demo.
See the source code.
Related
I've got a service that I use to share data between 2 components. That part works flawlessly, but now I need to call a method of component A, when something triggers on the service (and pass a value to that component). How can I do this? I read on older questions that this is a wrong approach but since Im a noob I dont know what to search for a solution.
Do I need to use observables?
I think Joseph's idea is the way to go.
Here's how I'd implement it:
class FooService {
private _newEvents = new Subject();
newEvents$ = this._newEvents.asObservable();
addNewEvent (ev) {
this._newEvents.next(e);
}
}
// Allow `A` class to communicate with `B` class
class A {
addEvent (ev) {
this.fooService.addNewEvent(ev);
}
}
class B {
private subscription: Subscription;
ngOnInit () {
this.subscription = this.fooService.newEvents$
.subscribe(e => {})
}
ngOnDestroy () {
this.subscription.unsubscribe();
}
}
Note that if your B class subscribes to multiple observables, you should unsubscribe from them using, among other solutions, takeUntil.
Observables / Subjects are one way. You would have one Subject in the service, and would use .next(value) on it to exchange values. Each component which is interested in the value may subscribe to that subject.
Example: (taken from RxJS docs
//your Service
import { Subject } from 'rxjs';
const subject = new Subject<number>();
//Component A (and others as well)
service.subject.subscribe({
next: (num) => console.log(num)
});
//this should work as well with prettier syntax:
service.subject.subscribe(sum =>
console.log(num)
);
//Component B
service.subject.next(7) //passing number 7 to Component A
Whenever you create a subscription, make sure to always unsubscribe! Else you might end up with stacks of subscriptions, which will all get triggered simultaneously in the very same component.
From personal experience, I found it more helpful to outsource any functions and variables that could be considered as global into a dedicated service, if possible. If you directly read the variables of a service from your components (and modify them if necessary), you'll have the same effect. That works as long as you keep a proper service structure. Some examples of dedicated services with global use are:
Translations (TranslationService)
Rights Management (PermissionService)
Im pretty new to Angular 8 and RxJS and stumbled upon an issue:
I make an angular app which relies heavily on an externally loaded THREE.js scene. Services handle those scenes.
So most of the time there is No HTML Template (only maintaining scene via js object) => no bindings ?
So I was thinking... is there a way to use Rxjs subjects/observables to achieve something like Input() binding?
thats what i basically want to
this.sub = myService.watch('window.deviceorientation')
.subscribe({next(x => { if(x) this.sub.unsubscribe; doStuff();})})
I want to continously check a certain object (any really), be notified as soon as it exists (or instantly if it already does). I bet there is some weird combination of RxJS Operators which can do exactly this?
(So basically its a little bit like AngularJS scope.$watch but to keep performance I'd of course clean up subscriptions.)
You can use the fromEvent rxjs creation utility to achieve the desired effect.
import { fromEvent } from 'rxjs';
orientation$ = fromEvent(window, 'deviceorientation');
const subscription = orientation$.subscribe((event)=>{
//do stuff
});
// when disponse is required
subscription.unsubscribe();
So I worked my way around this.
I created a root-level component that allows me to watch for specific changes on scope:
//HTML
<app-change-detector [watch]="window.anyObject" (onChange)="anyRootService.onObjectChanged($event)">
//TS
#Component({
selector: 'app-change-detector'
})
export class ChangeDetectorComponent implements OnChanges {
#Input() watch: any;
#Output() onChange = new EventEmitter<any>();
constructor() { }
ngOnChanges(changes: SimpleChanges): void {
if (changes && changes.watch) {
this.onChange.emit(changes.watch.currentValue);
}
}
}
I cant seem to figure out what the issue is,
I have a service that has a behavior subject like so...
popupSource = new BehaviorSubject<any>('');
popup(component) {
this.popupSource.next(component);
}
then In my header component
popupClick() {
this._service.popup('example');
}
then in my header.html
<button (click)="popupClick()"></button>
then In my app component
ngOnInit() {
this._service.popupSource.subscribe((result) => {
console.log(result);
})
}
so whats happening is the click is firing the this._service.popup('example'); but its never hitting the subscription...
I've put breakpoints on each function and It succesfully reaches this.popupSource.next(component) but then nothing?? Every Time I click the button I should be getting the console log.
Im not sure what Im doing wrong... Ive left out code for brevity sake so please let me know if you need more information
EDIT
Ive also tried doing
private popupSource = new BehaviorSubject<any>('');
getPopup = this.popupSource.asObservable();
popup(component) {
this.popupSource.next(component);
}
and the in my app component listend to the getPopup instead
ngOnInit() {
this._service.getPopup.subscribe((result) => {
console.log(result);
})
}
and that's not working either, I cant figure out what the problem is...
Thanks
Your issue here is that you provide your service in two different modules, that end up with two instances of your service. The simplest way if you are using Angular v6 is to use the providedIn flag in your service:
import { Injectable } from '#angular/core';
#Injectable({providedIn: 'root'})
class myService {}
With this way you don't need to provide your service in the providers array of any module, it will be automatically provided in the root injector.
Documentation about this can be found here : Angular providers.
If your are using Angular v5 you should only provide your service in your AppModule.
In your service, write a new method like this:
popupSource = new BehaviorSubject<any>('');
getPopupSource() {
return this.popupSource.asObservable();
}
And subscribe to that instead of subscribing directly to the subject itself. You could just add the 'asObservable()' part whenever you subscribe to it instead of creating an entirely new method, but I like the separate method so I can keep the Subject private, and I usually subscribe to something multiple times in different places throughout an app, so it cuts down on repetition.
In your component :
this._service.getPopupSource().subscribe( result => { etc...})
EDIT :
Demo recreation of your scenario - https://stackblitz.com/edit/angular-n6esd5
You may not have the same instance of service, my same problem was that I had #Injectable({ providedIn: 'root'}) for my service, but also I put this service in the component providers [] array, just remove the providers array, then it works
I'm currently trying to get a Register/Subscribe system to work with RxJs.
The situation is that I have component A with several sub components A1, A2, A3, ... The amount has to be dynamic. What I want to do now is that whenever an event I will call "somethingChanged" occurs (which is already distributed through an Observable) all sub components A1, ... will do some processing and then return some information (a state) as an event I'll call newStates to the parent action A probably using another observable. For this to work the sub components first have to register themselves to the "event manager" as children of A so that these events can be processed accordingly.
First idea
My first idea for this was to use a bufferCount on the newStates observable with the count being the amount of registered sub components. The problem is that the sub component registering and the parent component subscribing to the newStates observable is happening at almost the same time, the parent even being slightly faster which means the amountSub is usually 0 which breaks this attempt.
registerSubComponent() {
amountSub++;
}
getParentObservable() {
return newStates.bufferCount(amountSub).mergeMap();
}
Second idea
The second attempt was to use the somethingChanged Event and use that to initialize a takeLast to get the last items when they should be thrown. The problem is again as i will run into race condition as sub components take longer to throw their newStates events meaning I'll get old values.
registerSubComponent() {
amountSub++;
}
getParentObservable() {
return somethingChanged.map(() => newStates.takeLast(amountSub);
}
Third idea
So currently my only idea would be to catch the newStates event in the event manager, store the states in an array and check everytime if all registered components send them by looking at the array length. When all states are in i could then send the saved states and reset the array.
registerSubComponent() {
amountSub++;
}
getParentObservable() {
return newParentObservable;
}
newStates.subscribe(state => {
savedStates.push(state);
if(savedStates.length == amountSub) {
newParentObservable.next(savedStates);
savedStates = [];
}
});
Is this the only way or am I missing something so it could be done easier/with observables?
Btw: This is all pseudo code as my actual code also has to support multiple parent components in one manager making it cumbersome to read through.
It sounds like you want change detection up the tree. Using the following method with an angular service sounds like it might be what you need:
I found a solution on this guy Jason Watmore's blog that describes using rxjs Observables and Subjects. Using this method allows data changes to easily propagate up and down the angular inheritance tree to any component you want
Jason's Post
Accompanying Plunkr
Briefly:
You declare a service as a provider at the module.ts level with 3 methods:
sendMessage
clearMessage
getMessage
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class MessageService {
private subject = new Subject();
sendMessage(message: string) {
this.subject.next({ text: message });
}
clearMessage() {
this.subject.next();
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
}
This service needs imports of Observable and Subject from rxjs
In each component you want to share data with:
create a subscription object in the constructor which calls the service.getMessage() function
call rxjs subscription.unsubscribe() in ngOnDestroy for each component so you aren't leaking memory
you can hook in a function to handle the incoming subscription updates
When you have data you want to share with your other components:
Create a public method which calls the service.sendMessage() method
This will send your updated data to each component and fire those functions you've hooked in to handle the changed data
I believe the blog post I linked to and the plunkr from the post say it best and have really helped me move data around efficiently in my own app but if you have any questions I'll do my best to answer them
I have a parent component that retrieves an array of objects using an ajax request.
This component has two children components: One of them shows the objects in a tree structure and the other one renders its content in a table format. The parent passes the array to their children through an #input property and they display the content properly. Everything as expected.
The problem occurs when you change some field within the objects: the child components are not notified of those changes. Changes are only triggered if you manually reassign the array to its variable.
I'm used to working with Knockout JS and I need to get an effect similar to that of observableArrays.
I've read something about DoCheck but I'm not sure how it works.
OnChanges Lifecycle Hook will trigger only when input property's instance changes.
If you want to check whether an element inside the input array has been added, moved or removed, you can use IterableDiffers inside the DoCheck Lifecycle Hook as follows:
constructor(private iterableDiffers: IterableDiffers) {
this.iterableDiffer = iterableDiffers.find([]).create(null);
}
ngDoCheck() {
let changes = this.iterableDiffer.diff(this.inputArray);
if (changes) {
console.log('Changes detected!');
}
}
If you need to detect changes in objects inside an array, you will need to iterate through all elements, and apply KeyValueDiffers for each element. (You can do this in parallel with previous check).
Visit this post for more information: Detect changes in objects inside array in Angular2
You can always create a new reference to the array by merging it with an empty array:
this.yourArray = [{...}, {...}, {...}];
this.yourArray[0].yourModifiedField = "whatever";
this.yourArray = [].concat(this.yourArray);
The code above will change the array reference and it will trigger the OnChanges mechanism in children components.
Read following article, don't miss mutable vs immutable objects.
Key issue is that you mutate array elements, while array reference stays the same. And Angular2 change detection checks only array reference to detect changes. After you understand concept of immutable objects you would understand why you have an issue and how to solve it.
I use redux store in one of my projects to avoid this kind of issues.
https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
You can use IterableDiffers
It's used by *ngFor
constructor(private _differs: IterableDiffers) {}
ngOnChanges(changes: SimpleChanges): void {
if (!this._differ && value) {
this._differ = this._differs.find(value).create(this.ngForTrackBy);
}
}
ngDoCheck(): void {
if (this._differ) {
const changes = this._differ.diff(this.ngForOf);
if (changes) this._applyChanges(changes);
}
}
It's work for me:
#Component({
selector: 'my-component',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.scss']
})
export class MyComponent implements DoCheck {
#Input() changeArray: MyClassArray[]= [];
private differ: IterableDiffers;
constructor(private differs: IterableDiffers) {
this.differ = differs;
}
ngDoCheck() {
const changes = this.differ.find(this.insertedTasks);
if (changes) {
this.myMethodAfterChange();
}
}
This already appears answered. However for future problem seekers, I wanted to add something missed when I was researching and debugging a change detection problem I had. Now, my issue was a little isolated, and admittedly a stupid mistake on my end, but nonetheless relevant.
When you are updating the values in the Array or Object in reference, ensure that you are in the correct scope. I set myself into a trap by using setInterval(myService.function, 1000), where myService.function() would update the values of a public array, I used outside the service. This never actually updated the array, as the binding was off, and the correct usage should have been setInterval(myService.function.bind(this), 1000). I wasted my time trying change detection hacks, when it was a silly/simple blunder. Eliminate scope as a culprit before trying change detection solutions; it might save you some time.
Instead of triggering change detection via concat method, it might be more elegant to use ES6 destructuring operator:
this.yourArray[0].yourModifiedField = "whatever";
this.yourArray = [...this.yourArray];
You can use an impure pipe if you are directly using the array in your components template. (This example is for simple arrays that don't need deep checking)
#Pipe({
name: 'arrayChangeDetector',
pure: false
})
export class ArrayChangeDetectorPipe implements PipeTransform {
private differ: IterableDiffer<any>;
constructor(iDiff: IterableDiffers) {
this.differ = iDiff.find([]).create();
}
transform(value: any[]): any[] {
if (this.differ.diff(value)) {
return [...value];
}
return value;
}
}
<cmp [items]="arrayInput | arrayChangeDetector"></cmp>
For those time travelers among us still hitting array problems here is a reproduction of the issue along with several possible solutions.
https://stackblitz.com/edit/array-value-changes-not-detected-ang-8
Solutions include:
NgDoCheck
Using a Pipe
Using Immutable JS NPM github