Angular2 dart ngFor not updating view when using WebSockets - websocket

I've been having a problem with an angular2 component. The view doesn't update when I'm using websockets. I've tried with http requests and it works fine, but I need to keep the data in the view updated.
I can connect to the server just fine using websockets and I can recieve the data, but my view doesn't update.
#Component(
selector: "pv-table",
templateUrl: "./table.component.html"
)
class TableComponent
{
WebSocket _connection;
// NgZone _zone;
// ApplicationRef _application_ref;
List data;
TableComponent(/* this._zone, this._application_ref */)
{
_connection = new WebSocket( "ws://${HOST}:${PORT}" );
_connection.onOpen.first.then( ( _ ) {
_connection.onMessage.listen( ( MessageEvent e ) => OnMessage( e.data ) );
// A simple class that gives my application a standard
// way to communicate.
Packet request = new Packet()
..message = "table.get"
..data = {};
_connection.send( JSON.encode( request ) );
_connection.onClose.first.then( ( _ ) { });
});
}
void OnMessage( String data )
{
Map res = JSON.decode( data );
String cmd = res[ "message" ];
Log.Info( cmd );
switch( cmd )
{
case "table.send":
// Setting the data here. This works
data = res[ "data" ];
// This prints the correct data in the developer console.
Log.Info( data );
// Neither of these help.
// _zone.run( () => data = res[ "data" ] );
// or
// _application_ref.tick();
break;
default:
Log.Warn( "Unknown command: $cmd" );
break;
}
}
}
I've googled around and seen some problems like this where forcing change decection with NgZone.run(...) or ApplicationRef.tick(), but that didn't help. Either they don't work for this situation or I don't know how to use them.
My template looks like this:
<p *ngFor="let person of data">
<span scope="row">{{ person[ "_id" ] }}</span>
<span>{{ person[ "name" ] }}</span>
<span>{{ person[ "job" ] }}</span>
<span>{{ person[ "date_due" ] }}</span>
</p>
As a comment mentions. I can see the data being printed in the developer console. It is in the correct format, a list of maps with the correct fields, but the page remains blank.

Turns out I was just being really, really stupid.
Lets just take a look at a few things shall we?
The signature for the method that handles incoming data:
void OnMessage( String data )
The member that I am trying to use in the template:
List data;
The assignment inside of the OnMessage method:
data = res[ "data" ];
Notice anything strange?
Maybe that the class member and the method parameter have the same name?
Maybe that means the parameter is shadowing the member?
Maybe that means an assignment to that name is actually to the parameter and not the member?
The worst part is I've been sat here for almost two hours trying to figure out what the problem was.
Changing two lines and everything works
void OnMessage( String data ) => void OnMessage( String response_data )
Map res = JSON.decode( data ); => Map res = JSON.decode( response_data );

Related

CKEditor5: How to create model element value from data attribute?

I have legacy html data that I'm trying to edit with CKEditor5. The data format is:
<my-data-element url="https://something.com/more/stuff"></my-data-element>
The desired model format is
<my-model-element>
https://something.com/more/stuff
</my-model-element>
where the url attribute in the data is now the text of the model element. my-model-element is an editable widget so the user can easily modify the existing URL, copy/paste/etc. When the model is convert to data, the text in my_model-element should be converted to the url value for my-data-element. Reading the value of the url attribute is relatively easy, but I can't figure out how to set the text of the my-model-element. While this looks similar to a link, it's not a link. I considered borrowing from the link editing code, but that's a lot of code and this should be a root level object.
For data down casting, extracting the value of the element to set as the url is easy. The code below leaves the text of my-model-element in my-data-element but I can deal with that for now. It also results in my-data-element having the attribute undefined="undefined", for some reason, but I can also live with that.
schema.register( 'my-model-element', {
isObject: true,
allowWhere: '$block',
allowAttributes: ['url'],
allowContentOf: '$block'
} );
conversion.for( 'dataDowncast' ).elementToElement( {
model: 'myElement',
view: ( modelItem, {writer: viewWriter } ) => {
const data = modelItem.getChild(0).data;
const elem = viewWriter.createContainerElement (
'my-data-element', { url: data }
);
return elem;
}
} );
conversion.for( 'dataDowncast' ).attributeToAttribute( {
model: 'url',
// view has to be a function or the url doesn't get updated
view: () => 'url',
});
For up casting I can get the url from my-data-element, but have not been successful setting the text of my-model-element. Instead, the text value of my-model-element remains empty.
conversion.for( 'upcast' ).elementToElement( {
model: ( viewElement, {writer: modelWriter }) => {
// Pulling the URL works
const url = viewElement.getAttribute('url');
// But creating the child of the new element doesn't
const text = modelWriter.createText(`${url} DOESNT WORK`);
const elem = modelWriter.createElement('my-model-element', {}, text);
return elem;
},
view: {
name: 'my-data-element',
}
} );
I've read the majority of the CKEditor5 documentation on up and down casting, and the tutorials on block, inline, and data driven widgets.

rxjs why does behaviour subject when piped triggered with next()

I have read the tutorial from this link https://magarcia.io/2019/02/18/bloc-pattern-with-react-hooks
and i just dont understand how the search query to the API is triggered when _query.next is called with new search terms
see below code.
export class SearchBloc {
private _results$: Observable<string[]>;
private _query$ = new BehaviorSubject<string>('');
constructor(private api: API) {
**this._results$ = this._query$.pipe(
switchMap((query) => {
return observableFrom(this.api.search(query));
})
);**
get results$(): Observable<string[]> {
return this._results$;
}
}
const SearchInput = () => {
const searchBloc = useContext(SearchContext);
const [query, setQuery] = useState('');
useEffect(() => {
searchBloc.query.next(query);
}, [searchBloc, query]);
return (
<input
type="text"
name="Search"
value={query}
onChange={({ target }) => setQuery(target.value)}
/>
);
};
Assuming that searchblock was put in the context, and during input change the query which is a behaviour subject is assigned a new value with next();
how or why does the api query executes?
I guess I did not understand the line with
this._results$ = this._query$.pipe(
switchMap((query) => {
so maybe the question is, how did the pipe worked? did it create a method callback that will execute when next is called? and what is the assignment to result mean?
anyone that can help me make sense of it is greatly appreaciated.
Consider the following code:
It creates a stream of 5 numbers. Then it creates a second stream which is defined as a stream that has all the same numbers as the first one, only each number is incremented.
const numberStream$ = of(1,2,3,4,5);
const numbersPlus1$ = numberStream$.pipe(
map(v => v + 1)
);
numbersPlus1$.subscribe(console.log);
If you subscribe to numberStream$ you should expect to get 1,2,3,4,5.
If you subscribe to numbersPlus1$ you should expect to get 2,3,4,5,6.
Here we do the same thing with a Subject. Of course, unlike of(1,2,3,4,5), a subject lets you create a stream imperatively. Whenever I call .next on a subject, I'm saying "Make this value the next emission in this subject's stream."
const numberSubject$ = new Subject<number>();
const numbersPlus1$ = numberSubject$.pipe(
map(v => v + 1)
);
numbersPlus1$.subscribe(console.log);
numberSubject$.next(1);
numberSubject$.next(2);
numberSubject$.next(3);
numberSubject$.next(4);
numberSubject$.next(5);

Angular with Akita and Rxjs having issue with foreach inside outer observable

I am new to Angular with Akita.
I have an application where the users are loaded and set in the store. all user have an initial imageUrl property set to eg: 'http://testxxxxxx'
The component query the store for all users, and pipe to calls a method which loops through each person, make an api call to get 'blob' image response from api, and update the store with the person's imageUrl = 'blob:http:2fk2fjadkf' and the component set the <img src='imageUrl'.
But for some reason the method inside the outer observable is looping for many times. not sure why.
Here is my code:
Component:
peopleToShow$ = this.peopleFacade.peopleToShow$;
Component.html uses peopleToShow$ and the imageUrl property of each person. Right now it is not taking the updated blob url which is set in the this.loadImagesAsBlobs
Facade:
peopleToShow$ = this.peopleQuery.peopleToShow$
.pipe(
tap((people) => this.loadImagesAsBlobs(people))
);
private loadImagesAsBlobs(people: Person[]) {
people.forEach((person) => {
if (!person.isUrlChecked) {
this.imageDataService
.getAndStoreImageOnClient(person.imageUrl)
.pipe(
take(1),
switchMap((safeUrl) => {
this.updatePersonWithBlobImageUrl(person.id, safeUrl);
return EMPTY;
}),
catchError(() => {
this.updatePersonWithBlobImageUrl(person.id, null);
return EMPTY;
})
)
.subscribe();
}
});
}
private updatePersonWithBlobImageUrl(id: number, blobUrl: SafeUrl) {
this.peopleStore.updatePersonWithBlobImageUrl(id, blobUrl as string);
}
Thanks
It's not within this code, but when I've had this problem, it was because I had multiple things listening to a single observable, which means it was all happening several times.
To fix, change
peopleToShow$ = this.peopleQuery.peopleToShow$
.pipe(
tap((people) => this.loadImagesAsBlobs(people))
);
to
peopleToShow$ = this.peopleQuery.peopleToShow$
.pipe(
tap((people) => this.loadImagesAsBlobs(people)),
share()
);
or use shareReplay(1) instead, if you're worried about peopleToShow$ emitting before everything downstream is set up.

put the values in the object and best way for get old value

Good morning all,
Here I have two small questions I think my code is not optimized and I would like some of your help because I try more solution but it does not work.
I use v-model to modify my object but one can not save the modifications so I will search in axios the object in my DB and then I give him the initial values. my object comes from a parent component for start component, in the parent I make a v-for
<span v-if="number.edit">
<i class="fas fa-window-close text-danger" #click="onClickClose"></i>
</span>
onClickClose(){
axios.get('number/'+this.number.ref)
.then(({data: data}) => {
console.log(data);
this.number.status = data[0].status;
this.number.number = data[0].number;
this.number.route = data[0].route;
this.number.uri = data[0].uri;
this.number.refcli = data[0].refcli;
this.number.refcarrier = data[0].refcarrier;
this.number.porting_date = data[0].porting_date;
this.number.portout_date = data[0].portout_date;
this.number.emergency_active = data[0].emergency_active;
this.number.emergency_zipcode = data[0].emergency_zipcode;
this.number.memo = data[0].memo;
this.number.carrier = data[1];
this.number.edit = null;
});
},
Is there any other way than doing this on axios? a place where the basic values are stored even after modification (VueX?) and then do other than each field manually from my object. I would like something like that.
axios.get('number/'+this.number.ref)
.then(({data: data}) => {
console.log(data);
this.number ={
status : data[0].status,
number : data[0].number,
route : data[0].route,
uri : data[0].uri,
refcli : data[0].refcli,
refcarrier : data[0].refcarrier,
porting_date : data[0].porting_date,
portout_date : data[0].portout_date,
emergency_active : data[0].emergency_active,
emergency_zipcode : data[0].emergency_zipcode,
memo : data[0].memo,
carrier : data[1],
edit : null,
}
});
Thank's for your help, excuse me if I express myself badly. Let me know and I'll explain it better.
try shortening your code to this (mostly es6):
.then(({ data: {0: firstElData}, {1: secondElData }) => {
const { status, number, route, uri, refcli, refcarrier, porting_date,
portout_date, emergency_active, emergency_zipcodem memo } = firstElData
this.number = { status, number, route, uri, refcli, refcarrier,
porting_date, portout_date, emergency_active, emergency_zipcodem memo }
this.number.carrier = secondElData
this.number.edit = null;
Although I am not 100% sure what you are asking.
Of course if all you need is to store original value then put them in a variable when they come in originalData and then you can access that later no need for a store for that.

Angular 2 component global variable is undefined?

Im trying to create an if statement within a template to display a certain block of text when an array length is < 1.
This is how I try to do it:
<div *ngIf="socialNetworks.length > 1">
<div class="alert-box warning radius">You can still connect some accounts</div>
<button class="btn btn-primary" *ngFor="let network of socialNetworks" (click)="loginSocialNetwork(network)">
{{network.title}}
</button>
</div>
But I always get an error saying that it cannot read property length of undefined.
I define the variable socialNetworks[] here in an Angular 2 component:
export class MyAccountComponent implements OnInit {
socialNetworks: socialNetwork[];
campaigns: Campaign[];
showGreeting: boolean = true;
constructor(
private _dataservice: DataService) {
}
Then, in a seperate method, I set the value from a response from a pyramid view here:
getSocialNetworks() {
var url: string;
url = "/account_api"
this._dataservice.getDataByUrl(url)
.subscribe(
res => this.socialNetworks = res,
err => this.logError(err)
)
}
Even if I add a console.log statement at the end here to see the value of this.socialNetworks, it says it is undefined. But in a debugger I can see that the value of this.socialNetworks is not undefined.
So my question is, am I just referencing the global variable incorrectly, or am I missing/misunderstanding something all together?
Looks to me like socialNetworks isn't set as an empty array on construction, so it will be undefined on init. Try to change the top socialNetwork to:
socialNetworks: socialNetwork[] = [];
The issue we were speaking about below in the comments is most likely to do with this within the subscribe method. It is assigning it to the incorrect scope.
try below:
getSocialNetworks() {
var _that = this;
var url: string;
url = "/account_api"
this._dataservice.getDataByUrl(url)
.subscribe(
res => _that.socialNetworks = res,
err => _that.logError(err)
)
}
If the api call uses some external library it might not be hooked into angular 2's digest cycle (Facebook SDK isn't for example). You can debug using Augury, or for simplicity set a global window property in your constructor to access your component:
constructor(private _dataservice: DataService) {
window['MAC'] = this;
}
Then you can check in the dev tools of your browser to see if the socialNetworks array is actually being set:
MAC.socialNetworks
You can always in your HTML use some quick code to show it as JSON to debug what is going on:
{{socialNetworks | json}}
If the page doesn't show it having a value but you see it in the console, then your _dataService isn't triggering change detection and you have to do it manually. You can use ApplicationRef by injecting it in your constructor and calling tick() on it after you set your data.
constructor(
private _dataservice: DataService,
private _appref: ApplicationRef ) {
}
getSocialNetworks() {
var _that = this;
var url: string;
url = "/account_api"
this._dataservice.getDataByUrl(url)
.subscribe(
res => {
_that.socialNetworks = res;
this._appref.tick(); // force change detection
}, err => _that.logError(err)
)
}
As for the calling length on undefined, since you're asynchronously pulling data your socialNetworks property is undefined to start. You can initialize it to an empty array like in the answer by #JacobS or modify your check to account for it:
<div *ngIf="socialNetworks && socialNetworks.length > 1">

Resources