I've been working on something that uses a shared dart package through for firestore and come across an interesting issue.
I have a business logic object that is basically as follows:
class HomeBloc {
final Firestore _firestore;
CollectionReference _ref;
HomeBloc(this._firestore) {
_ref = _firestore.collection('test');
}
Stream<List<TestModel>> get results {
return _ref.onSnapshot.asyncMap((snapshot) {
return snapshot.docs.map((ds) => TestModel(ds.get('data') as String)).toList();
}
}
}
Given the following code component:
#Component(
selector: 'my-app',
templateUrl: 'app_component.html',
directives: [coreDirectives],
pipes: [commonPipes]
)
class AppComponent extends OnInit {
HomeBloc bloc;
Stream<List<TestModel>> results;
AppComponent() {
}
#override
void ngOnInit() {
print("Initializing component");
fb.initializeApp(
//...
);
getData();
}
Future<void> getData() async {
final store = fb.firestore();
bloc = HomeBloc(store);
}
}
I would expect the following to work, but it does not:
<div *ngIf="bloc != null">
<h2>Loaded properly</h2>
<ul>
<li *ngFor="let item of bloc.results | async">
{{item.data}}
</li>
</ul>
</div>
However, if I instead change getData and the html to the following:
Future<void> getData() async {
final store = fb.firestore();
bloc = HomeBloc(store);
results = bloc.results;
}
// HTML
<ul *ngFor="let item of results | async">
Everything works as expected. What's going on here?
The answer is that the get method is creating a new list every time its accessed, which isn't giving Angular an oppotunity to render the items before resetting. The correct implementation of HomeBloc:
class HomeBloc {
final Firestore _firestore;
CollectionReference _ref;
HomeBloc(this._firestore) {
_ref = _firestore.collection('test');
_results = _ref.onSnapshot.asyncMap((snapshot) {
return snapshot.docs.map((ds) => TestModel(ds.get('data') as String)).toList();
}
Stream<List<TestModel>> _results;
Stream<List<TestModel>> get results => _results;
}
Related
How can we share data between two components - both are completely separate components? (which are not in a child-parent relationship)
I want to show my registration component's variable 'totalReg' value in my header component. Both files are below.
This is my reg.component.ts
import { Component, Output } from '#angular/core';
import { UserService } from '../services/reg.service';
import { VERSION } from '#angular/core';
#Component({
templateUrl: 'reg.component.html'
})
export class RegComponent {
constructor(
private userService: UserService,
) { }
#Output() totalReg: any;
register(event: any) {
this.userService.create(event.target.username.value)
.subscribe(
data => {
this.totalReg = data['data'].userId;
console.log(this.totalReg); // Navigate to the
listing aftr registration done successfully
},
error => {
console.log(error);
});
}
}
This is my header.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
This is html of my header component header.component.html
<div class="container">
<mat-toolbar>
<ul class="nav navbar-nav">
<li><a [routerLink]="['/login']">Login</a></li>
<li><a [routerLink]="['/reg']">Registration</a>
</li>
<li><a [routerLink]="['/users']">All Users</a>
</li>
</ul>
</mat-toolbar>
<span>{{totalReg}}</span>
</div>
header component should show the value of totalReg .
you can do this with help of service class.
you are already using the UserService in RegComponent , so use the same service in the HeaderComponent to get the data.
HeaderComponent.ts
export class HeaderComponent implements OnInit {
totalReg: any;
constructor(private service : UserService) { }
ngOnInit() {
this.totalReg = this.service.totalRg;
}
}
RegComponent.ts
export class RegComponent {
#Output() totalReg: any;
constructor(private userService: UserService) { }
register(event: any) {
this.userService.create(event.target.username.value)
.subscribe(data => {
this.totalReg = data['data'].userId;
console.log(this.totalReg); // Navigate to the listing aftr registration done successfully
this.service.totalRg = this.totalReg;
},
error => {
console.log(error);
});
}
}
you are already using a class in service class you need add the variable as totalRg
UserService.ts
export class UserService {
totalRg:any;
constructor() { }
create(name: any) {
return ....//
}
}
I am new to angular 7 and didn't find any proper answer for similar questions posted.
I am getting Property 'subscribe' does not exist on type 'void' in angular-cli. I tried importing subscribe from rxjs but didn't find that library.
The problem is in the UpdateRecord Function!
product.component.ts code:
the code bellow is exist in compoent.ts of product
import { Component, OnInit } from '#angular/core';
import { ProductService } from 'src/app/shared/product.service';
import { NgForm } from '#angular/forms';
import { ToastrService } from 'ngx-toastr';
import { filter, map } from 'rxjs/operators';
#Component({
selector: 'app-product',
templateUrl: './product.component.html',
styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {
constructor(private service : ProductService, private toastr : ToastrService) { }
ngOnInit() {
this.resetForm();
}
resetForm(form?: NgForm) {
if (form != null)
form.resetForm();
this.service.formData = {
ProductID: null,
ProductName: '',
ProductDescription: '',
Price: 0.00,
Image: '',
Qte: null
}
}
onSubmit(form: NgForm) {
if (form.value.ProductID == null)
this.insertRecord(form);
else
this.updateRecord(form);
}
insertRecord(form: NgForm) {
this.service.postProduct(form.value).subscribe(res => {
this.toastr.success('Inserted successfully', 'Product. Register');
this.resetForm(form);
this.service.refreshList();
});
}
updateRecord(form: NgForm) {
this.service.putProduct(form.value).subscribe(res => {
this.toastr.success('Updated successfully', 'Product. Update');
this.resetForm(form);
this.service.refreshList();
});
}
}
product.service.ts code :
the code bellow is exist in service file related to product
import { Injectable } from '#angular/core';
import { Product } from './product.model';
import { HttpClient } from "#angular/common/http";
#Injectable({
providedIn: 'root'
})
export class ProductService {
formData : Product
list : Product[]
readonly rootURL= 'http://localhost:50369/api'
constructor(private http : HttpClient) { }
postProduct(formData : Product){
return this.http.post(this.rootURL+'/Product', formData);
}
refreshList(){
return this.http.get(this.rootURL+'/Product')
.toPromise().then(res => this.list = res as Product[]);
}
putProduct(formData : Product){
this.http.put(this.rootURL+'/Product/'+formData.ProductID,FormData);
}
}
Thanks in advance,
I missed return :
So in putProduct function in product.service.ts is updated to be :
putProduct(formData : Product){
return this.http.put(this.rootURL+'/Product/'+formData.ProductID,FormData);
}
And it's working now!
Your HttpClient.put function seems to be incorrectly used (you are passing the class as parameter when you should be passing the object).
Look for the function updateHero() in this StackBlitz example.
/** PUT: update the hero on the server. Returns the updated hero upon success. */
updateHero (hero: Hero): Observable<Hero> {
httpOptions.headers =
httpOptions.headers.set('Authorization', 'my-new-auth-token');
return this.http.put<Hero>(this.heroesUrl, hero, httpOptions)
.pipe(
catchError(this.handleError('updateHero', hero))
);
}
I have this component
#Component({
templateUrl: './app/component/template/actualstate.template.html',
styleUrls: ['./app/component/style/actualstate.style.css'],
pipes: [MomentPipe, CapitalizePipe]
})
export class ActualStateComponent implements OnInit {
public room: Room;
constructor(private roomService: RoomService) {
roomService.roomSelected$.subscribe(room => this.onRoomSelected(room));
}
onRoomSelected(room: Room) {
this.room = room;
console.log("room", room);
}
}
and this other component
#Component({
templateUrl: './src/admin/template/admin.template.html',
styleUrls: ['./src/admin/style/admin.style.css'],
providers: [UserService]
})
export class AdminComponent{
constructor ( private roomService: RoomService) {
}
onClick () {
this.roomService.selectRoom("","");
this.router.navigate(['ActualState']);
}
}
}
, this service :
#Injectable()
export class RoomService {
private route_room = "public/mock/room.json";
public roomSelected$: EventEmitter<Room>;
constructor (private http: Http) {
this.roomSelected$ = new EventEmitter();
}
public selectRoom (subdomain: string, id: string) {
// pick the right room
let room = ...
this.roomSelected$.emit(room);
}
private handleError (error: Response) {
return Observable.throw(error.json().error || 'Server error');
}
}
And this template :
<div class="actual-state" *ngIf="room">
<h3>Salle {{ room.name }}
</h3>
</div>
The purpose is :
Admin component (user click on some button)
-> Listener OnClick calls a method on service roomService
-> roomService emit an event (that is public)
-> appComponent listen to this event (.subscribe)
I have no clue why this is not working. The <h3> is never showing .. even though the console.log(room) display something in the console...
How does this data binding working ? Because it just looks like data are not two-way bound
...
EDIT : i understood the problem, it was related to the routing i made. in fact i did'nt understand the fact that component of a route is destroyed when you change the route
I guess you need to subscribe
return this.http.get(this.route_room)
.map(res => res.json())
.do(data => {
this.roomSelected$.emit(data);
})
.subscribe(value => {})
.catch(this.handleError);
i'm expanding angular.dart tutorial for some simple CRUD operations. I'm trying to do nice, smart combobox (dropdown box) that is taking List of KeyValuePairs and returning Selected KeyValyePair.
Unfortunatly i'm getting this error.
Model did not stabilize in 10 digests. Last 3 iterations:
itemsMap: collection: Instance of 'KeyValuePair'[null -> 0], Instance of 'KeyValuePair'[null -> 1]...
There is code.
dropdown_box.dart
library dropdown_box;
import 'package:angular/angular.dart';
import 'dart:core';
import 'package:tutorial/service/KeyValuePair.dart';
import 'dart:async';
#Component(selector: 'dropdownbox', templateUrl: 'dropdown_box.html', publishAs: 'dropCtrl')
class DropDownComponent {
// #NgOneWay('items-map')
// Map<String, String> itemsMap;
#NgOneWay('items-map')
List<KeyValuePair> itemsMap;
#NgTwoWay('selected-keyvalue')
KeyValuePair selectedKeyValue;
// void printit(item) {
// new Future(() {
// print("${item.value + ' '+ item.key}");
// SelectedKeyValue = new KeyValuePair(item.key, item.value);
// });
String selectedKey;
void setKeyAsSelected() {
new Future(() {
Iterable keyvaluepairs = itemsMap.where((i) => i.key == selectedKey);
if (keyvaluepairs.length > 0) {
selectedKeyValue = keyvaluepairs.elementAt(0);
}
});
}
}
dropdown_box.html
<div class=dropdownbox">
<select ng-model="selectedKey" ng-change="setKeyAsSelected()">
<option ng-value=""></option>
<option ng-repeat="item in itemsMap" ng-value="item.key">
{{item.value}}
</option>
</select>
</div>
use in recipe_book.html
<dropdownbox items-map="categorieKvList" selected-keyvalue="selectedKV"></dropdownbox>
recipe_book.dart (important part only)
KeyValuePair selectedKV;
List<KeyValuePair> _categorieKvList = new List<KeyValuePair>();
List<KeyValuePair> get categorieKvList {
_categorieKvList.clear();
categories.forEach((f)=>
_AddToList(f, f));
return _categorieKvList;
}
_AddToList(key, value)
{
KeyValuePair kvpair = new KeyValuePair.fromValues(key, value); //new KeyValuePair().AddValues(key, value);
_categorieKvList.add(kvpair);
}
keyvaluepair.dart
library KeyValuePair;
import 'package:angular/angular.dart';
#Injectable()
class KeyValuePair {
dynamic key;
dynamic value;
// AddValues(key, value) {
// this.key = key;
// this.value = value;
// }
KeyValuePair();
KeyValuePair.fromValues(dynamic this.key, dynamic this.value);
}
The getter of categorieKvList returns a changed _categorieKvList with new KeyValu pairs every time it is called. This is interpreted by angular as change.
Alter the logic to so _categorieKvList is modified only when its data source (categories) changes.
I'm trying to create an angular dart component dynamically. I know it's not a best practice but I have to because of how my angular widgets are being inserted.
I based my work off of:
How to add a component programatically in Angular.Dart?
The code samples on longer work because of changes in the Angular Dart library.
I got this code to work but it's inconsistent. The solution was the Timer.run() to fire the scope.apply. The problem with that is:
It stinks to make a call like that and would perform terribly with lots of components
It seems to work randomly. Most of the time it does but occasionally it doesn't do the {{foo}} replacements
void main() {
IBMModule module = new IBMModule();
AngularModule angularModule = new AngularModule();
Injector injector = applicationFactory()
.addModule(module)
.run();
AppComponent appComponent = injector.get(AppComponent);
appComponent.addElement("<brazos-input-string label='test'/>");
}
class MyValidator implements NodeValidator {
bool allowsElement(Element element) {
return true;
}
bool allowsAttribute(Element element, String attributeName, String value) {
return true;
}
}
#Injectable()
class AppComponent {
NodeValidator validator;
Compiler _compiler;
DirectiveInjector _directiveInjector;
DirectiveMap _directiveMap;
NodeTreeSanitizer _nodeTreeSanitizer;
Injector _appInjector;
Scope _scope;
AppComponent(this._directiveInjector, this._compiler, this._directiveMap, this._nodeTreeSanitizer, this._appInjector, this._scope) {
validator = new MyValidator();
}
void addElement(String elementHTML) {
DivElement container = querySelector("#container");
DivElement inner = new DivElement();
container.append(inner);
Element element = new Element.html(elementHTML, validator: validator);
// inner.setInnerHtml(elementHTML, validator: validator);
ViewFactory viewFactory = _compiler.call([element], _directiveMap);
if (_scope != null) {
Scope childScope = _scope.createProtoChild();
View newView = viewFactory.call(childScope, _directiveInjector);
newView.nodes.forEach((node) => inner.append(node));
Timer.run(() => childScope.apply());
} else {
print("scope is null");
}
}
}
class IBMModule extends Module {
IBMModule() {
bind(BrazosInputStringComponent);
bind(BrazosTextAreaComponent);
bind(BrazosButtonComponent);
bind(ProcessDataProvider, toImplementation: ActivitiDataProvider);
bind(AppComponent);
}
}