Share websocket data with multiple components in Angular 2 - javascript

I have 1 main component. Inside of this component i use WebSockets to receive data from the server. I receive an object with a range of fields inside it. The number of fileds can be from 0 till 10, for example. Each time i receive an object i use forEach() to take all fields. For each field i need to init a component, like this:
self.dcl.loadIntoLocation(StreamComponent, self.elementRef, 'stream');
If a copy of a component for current field of an object already exists, i need to update it with new received data within the view. The main my problem is i don't know how to pass the data from WebSockets to created component. I can create and init it, but i never mind how to pass data to it. Any ideas ?

You could try to leverage the promise returned by the method:
self.dcl.loadIntoLocation(
StreamComponent, self.elementRef, 'stream').then((compRef:ComponentRef) => {
compRef.instance.someAttr = something;
});
This promise allows you to get the instance of the newly created component and set data in its properties (for example, the web socket).

Related

Material Table not reflecting changes on datasource

This is my first question in Stack Overflow. I'll try to be specific but I don't know how to keep this short, so this is going to be a long post. Sorry about that. I promise I searched and tried a lot of stuff before asking, but I'm kind of lost now.
I'm developing a simple app in Angular 6 to keep track of software requisites and the tests associated to those requisites.
I have a component, called RequisiteList, whose HTML part consists in a mat-table with an Array of my own Requisite model class as [dataSource]. This array is received as an #Input parameter, and it also has an #Output parameter which is an EventEmitter that notifies and passes to the parent component every time a Requisite on the list is clicked.
I make use of RequisiteList inside of ReqListMain, which is a component consisting on the list and a hierarchical tree for filtering. This component is working fine, showing, and filtering requisites as intended. This component also captures the #Output event of the list and passes it as an #Output to its parent.
Finally (for what it's related to this question), I have a TestView component that has both an instance of RequisiteList to show the requisites currently associated to current test, and an instance of ReqListMain to add new requisites to current test (like a "browser"). This TestView has an instance of the model class Pectest corresponding to the test that is being currently visualized, which has an array of Requisite.
The idea in this last component was that whenever a requisite of the "browser" list was clicked, it was added to the current test's list. In order to do that, in the callback method associated to the #Output event of the browser list, I tried to add the Requisite received as a parameter:
addrequisite(requisite: Requisite) {
this.currentTest.requisites.push(requisite);
console.log('Current test: ');
console.log(this.currentTest);
}
In the HTML part of TestView, I inserted the RequisiteList component like this:
<app-requisitelist [requisites]="currentTest.requisites" ngModel name="reqlistview"></app-requisitelist>
(The ngModel property is part of the things I've been trying, I'm not sure it's necessary).
The result is:
The clicked requisite is not shown in the list.
In the console output I can see the content of currentTest object, and I verify that clicked requisites are in fact added to the requisites array of that object, so the event fires and the object is passed upwards by the children components.
I'm not sure if my problem is that data binding is made by value (I don't think so, as I bind an Array, which is an object AFAIK), or the table is not detecting data changes (I've tried to force data change detection with ChangeDetector), or anything else.
You pass a array to the app-requisitelist component. This component waits this array changes to update the content. When you do this.currentTest.requisites.push(requisite), the array this.currentTest.requisites doesn't change, I mean, if you do
const tmp = this.currentTest.requisites;
this.currentTest.requisites.push(requisite)
if (tmp === this.currentTest.requisites) {
console.log('The arrays are the same');
}
You will get the log printed. So, I suggest do something like that:
addrequisite(requisite: Requisite) {
this.currentTest.requisites.push(requisite);
this.currentTest.requisites = this.currentTest.requisites.map(item => item);
console.log('Current test: ');
console.log(this.currentTest);
}
The inserted line forces this.currentTest.requisites to be a new array with the same content.

Angular Material Table won't populate with external data on initial load

I'm working on an Angular 6 project where I'm loading data in from an AWS DynamoDB table via Observable into a Material Table component. I used the angular-cli to generate the initial structure of the table, and then added my own service to fetch external data, since the example used hard coded data in an array.
Everything seems to be working (I can see the correct data returned via console.log) except for the fact that on my initial load, the data that I'm returning from the observable isn't getting populated into the table. In fact if I inspect the "this.data" variable it seems like it's immediately getting set back to "undefined." If I select and change the number of results per page on the pagination component, the data returned by the observable is inserted.
connect(): Observable<DynamoTableItem[]> {
// Combine everything that affects the rendered data into one update
// stream for the data-table to consume.
const dataMutations = [
observableOf(this.data),
this.paginator.page,
this.sort.sortChange
];
// Set the paginators length
this.paginator.length = this.data.length;
return merge(...dataMutations).pipe(map(() => {
return this.getPagedData(this.getSortedData([...this.data]));
}));
}
I've put the project on stackblitz if someone wouldn't mind taking a look.
To reproduce the issue:
Go to: https://stackblitz.com/edit/mat-table-dynamo
Notice there is no data in the table.
Select the "Items per page" pulldown and change to a different value.
The table populates with the data returned from the Observable.
Thanks for your help!
The rule of thumb of writing any service in angular is that if you have a .subscribe() inside your service, you are probably(99%) do it wrong. Always return an Observable, and let your component do the .subscribe().
The reason why your code doesn't work is because you subscribe your data first inside your service, and then re-wrap it using Observable.of(). That won't work, because your http call is asynchronous. By the time your subscription inside your constructor has received emission, your connect has long established and this.data is first undefined before it can be assigned any values.
To solve your problem, simply change the of(this.data) to its original Observable source, and everything is working:
connect(): Observable<DynamoTableItem[]> {
// Combine everything that affects the rendered data into one update
// stream for the data-table to consume.
const dataMutations = [
this.dynamoService.getData(this.params),
this.paginator.page,
this.sort.sortChange
];
// Set the paginators length
this.paginator.length = this.data.length;
return merge(...dataMutations).pipe(map((received) => {
return this.getPagedData(this.getSortedData([...received]));
}));
}
Here is the working working StackBlitz

Angular.copy keeps giving me the same object

I have an AngularJS application that manages badges. In the application is a form to set the badge # and the name of the person it is assigned to, etc. This gets stored in $scope.badge.
When the user submits the form, I want to add the new badge to a list of badges, which is displayed below the form.
Partial code looks like this:
var badge = angular.copy($scope.badge); // make a copy so we don't keep adding the same object
$scope.badgeList.push(badge);
The first time I run this code, it adds the badge as expected.
Any subsequent time I run this code, the next badge REPLACES the previous badge in the badgeList. In other words, if I add 5 badges, the badgeList still only has 1 object in it because it just keeps getting replaced.
I'm thinking that this may be happening because the same object keeps getting added? Maybe I'm wrong? I am using angular.copy to try and avoid that happening, but it doesn't seem to be working.
Any thoughts on this?
$scope.badgeList.push(($scope.badge);
console.log($scope.badgeList)
no need to use angular.copy since you are ultimately storing all the badges in an array
angular.copy is used when you want to make a clone of object and not update the existing object and the clone's change are not reflected in main object.
If you just want to maintain a list of badges you can execute this block of code
like this
function addBadges(){
$scope.badgeList.push(($scope.badge);
console.log($scope.badgeList)
}
If you are refreshing the controller then obviously the variable will be reset and for such a case you need to make use of angular services.
Create a service and inside the service you need to define getter and setter method that will help in data persistence
and your bages array if saved in service will persist till the application is in foreground.
You could do something like this.
function addBadges(){
//initialize if undefined or null
if(!$scope.badgeList){
$scope.badgeList = [];
}
//Check if badge does not exists in the list
if ($scope.badgeList.indexOf($scope.badge) === -1) {
//Add to badge list
$scope.badgeList.push($scope.badge);
}
}

Remove data binding in angular2

Angular 2 data binding is great but i can't seem to find a angular 2 way of removing data binding on specific variables. My reason for this is i started hooking my application up to indexed DB and it works but i can't allow the temporary cache (just an array of all the indexed DB values) to be subject to data binding (if it was then the temporary cache would no longer mirror the database) my database is on an angular2 service. now i have found a way of removing the data binding but it isn't exactly pretty my code is this
app.copy=function(item){
return JSON.parse(JSON.stringify(item,app.replacer),app.reviver);
}
app.reviver=function(key,value){
if(value.fn){
value=new Function(value.parameters,value.body);
}else if(key==="time"){
value= new Date(value);
}
return value;
};
app.replacer=function(key,value){
if(typeof value ==="function"){
value=value.toString();
value={
fn:true,
parameters:value.match(/\(([\s\S]*?)\)/)[1].replace(/[\s\r\/\*]/g,""),
body:value.match(/\{([\s\S]*)\}/)[1].replace(/[\t\r\n]/g,"")
};
}
return value;
};
like i said it works but it isn't pretty. i can just run app.copy on the variables before they leave the cache so that they don't get data bound to anything. I was wondering if there was a cleaner way to tell angular 2 this variable isn't suppose to be data bound. and if not then at least i was able to get my solution up here for others.
If you establish "binding" imperatively you can stop the binding imperatively. There is currently no support in Angular2 to cancel a declarative binding imperatively.
Bind the view only to fields of the component.
Use observables in the service that fire an event when values change.
In the component subscribe to the observable and update the fields in the component when values in the service change.
Update values in the service when values change in the component.

How to trigger a Client JS method when a subscribed data is changed

I want my app to fire a method (client side) when a particular subscribed data is changed? for example, the client side has this following subscription
Meteor.subscribe('thePlayers');
thePlayers subscription returns a collection of data which is being displayed in the html through the template.
so whenever the collection get changed, Meteor automatically change the data in the HTML also. Besides this feature, I want my app to fire a method say fire() to be executed as soon as data get changed. What should i do to achieve this?
As David Weldon correctly, cursor.observerChanges is the way to go. Here's how you can use it for your example (assuming your collection is called thePlayers):
client-side
methodCaller = function (methodName) {
return function (/* arguments */) {
Meteor.apply(methodName, arguments)
}
}
var fireCaller = methodCaller('fire')
thePlayers.find().observeChanges({
added: fireCaller,
changed: fireCaller,
removed: fireCaller
})
In case you need this fire() to be run on server, you don't need a method, you can just rely on the observeChanges feature or just observe in your publication. See this question to get an example of how you can achieve that.
In case you need this fire() to be run on client, keep in mind that every helper in your template is reactive, that means it will re-run each time your collection is changed. I assume that it requires that you use the subscription inside it, but that needs to be confirmed.

Categories

Resources