Ngrx selector not triggering updates - javascript

My state is :
export interface RequestState {
tabsContent: TabContent[];
}
Where TabContent represent an array of visual tabs containing 1 Request each and additionnal infos :
export interface TabContent {
tabInfo: TabInfo;
request: Request;
results?: any[];
}
I have this specialized selector to get one Request from a single TabContent :
export const getRequestForTabInfo = (tabInfo: TabInfo) => createSelector(
getTabContent,
(tabContent: TabContent[]) => {
const tabContentFiltered = tabContent.filter(tab =>
tab.tabInfo.id === tabInfo.id
&& tab.tabInfo.index === tabInfo.index
&& tab.tabInfo.state === tabInfo.state);
if (tabContentFiltered && tabContentFiltered.length === 1) {
return tabContentFiltered[0].request;
}
return {} as Request;
},
);
and this basic one to get all TabsContent :
export const getTabContent = createSelector(
getRequestFeatureState,
state => state.tabsContent,
);
based on this FeatureSelector :
const getRequestFeatureState = createFeatureSelector<RequestState>('requests');
Defined in a facade service :
tabsContent$: Observable<TabContent[]>;
constructor(private store: Store<fromRequest.State>) {
this.tabsContent$ = this.store.pipe(
select(fromRequest.getTabContent),
takeUntil(this.componentDestroy()),
);
}
public getRequest(tabInfo: TabInfo): Observable<Request> {
return this.store.pipe(
select(fromRequest.getRequestForTabInfo(tabInfo)),
takeUntil(this.componentDestroy()),
);
}
and used in the component :
private initializeComponent(): void {
this.requestFacade.getRequest(this.tabInfo).pipe(
takeUntil(this.componentDestroy()),
).subscribe(request => {
console.log('request : ', request);
this.request = request;
});
this.requestFacade.tabsContent$.pipe(
takeUntil(this.componentDestroy()),
).subscribe(tab => {
console.log('tab : ', tab);
});
}
When the component is consctructed I get both logs, but when I update part of the related state (I update the content of one Request) only the tab log appear.
Why is the filtered selector observable not doing anything ?

You're using a static parameter in the getRequestForTabInfo selector so it presumes that it will not change overtime, which is why you are not getting the changes.
Instead of passing in parameters, I would suggest dispatching an action to set the selected tabinfo in the store and use selectors to retrieve it. You already have TabContents in store so should be able to reuse some of the logic you've already have.

Related

How to start iterableDiffer only after OnInit?

My problem is that the iterableDiffer detect a change when I only what to inialize the data from the database. I dont want to get this first change detection. I only want to get a change detection when the user edits the array.
I tryed to put the database request in the constructor. Didnt change anything.
constructor(
private route: ActivatedRoute,
private StoreService: StoreService,
private iterableDiffers: IterableDiffers
) {
this.iterableDiffer =
this.iterableDiffers.find([]).create(null);
}
myArray: MyClass[] = [];
iterableDiffer: IterableDiffer<unknown>;
ngOnInit() {
const id = this.route.snapshot.params.id;
this.StoreService.getData(id)
.subscribe(data => this.myArray = data);
// on this subscribe, the Differ falsely gets triggered
}
ngAfterViewChecked() {
const changes = this.iterableDiffer.diff(this.myArray);
if (changes) {
changes.forEachAddedItem((record: IterableChangeRecord<MyClass>) => {
console.log('Added Changes detected');
this.StoreService.addToDatabase(record.item);
});
}
}
// gets called by User Click, here i want to have the Differ called
// This works fine
addElmt(elmt: MyClass) {
this.myArray.push(elmt);
}
The code snippet is a simplified version of the real code. I can not call the StoreService.addToDatabase() in the function addElmt(). This is just for better explanation.
Thank you for your help!
Always can has a variable 'myObserver' and do
myObserver:any=null
ngAfterViewChecked() {
const changes = this.iterableDiffer.diff(this.myArray);
if (changes) {
changes.forEachAddedItem((record: IterableChangeRecord<MyClass>) => {
console.log('Added Changes detected');
this.StoreService.addToDatabase(record.item);
});
}
if (!myObserver)
{
const id = this.route.snapshot.params.id;
myObserver=this.StoreService.getData(id)
.subscribe(data => this.myArray = data);
}
}

How do I use axios response in different components without using export?

As the tittle says, I would like to be able to use the same axios response for differents components.
I have some restrictions like, I'm onlyl able to use react by adding scripts tags to my html so things like exports or jsx are impossible for me.
This is my react code:
class User extends React.Component {
state = {
user: {}
}
componentWillMount() {
console.log(localStorage.getItem("user"))
axios.get('http://localhost:8080/dashboard?user=' + localStorage.getItem("user"))
.then(res => {
const userResponse = res.data
setTimeout(() =>
this.setState({user: userResponse.user}), 1000);
})
}
render () {
const {user} = this.state
if (user.fullName === undefined)
return React.createElement("div", null, 'loading..');
return React.createElement("span", {className: "mr-2 d-none d-lg-inline text-gray-600 small" }, user.fullName);
}
}
ReactDOM.render( React.createElement(User, {}, null), document.getElementById('userDropdown') );
class Roles extends React.Component{
state = {
user: {}
}
componentWillMount() {
console.log(localStorage.getItem("user"))
axios.get('http://localhost:8080/dashboard?user=' + localStorage.getItem("user"))
.then(res => {
const userResponse = res.data
setTimeout(() =>
this.setState({user: userResponse.user}), 1000);
})
}
render () {
const {user} = this.state
const roles = user.user.roles.map((rol) => rol.roleName)
if (user.fullName === undefined)
return React.createElement("div", null, 'loading..');
return React.createElement("a", {className: "dropdown-item" }, user.fullName);
}
}
ReactDOM.render( React.createElement(Roles, {}, null), document.getElementById('dropdownRol') );
I would like to be able to manage different components(rendering each one) with data of the same axios response.
Is this possible considering my limitations?
Thanks in advance
Here's a working example of how you might do it. I've tried to annotate everything with comments, but I'm happy to try to clarify if you have questions.
// Fake response object for the store's "load" request
const fakeResponse = {
user: {
fullName: "Carolina Ponce",
roles: [
{ roleName: "administrator" },
{ roleName: "editor" },
{ roleName: "moderator" },
{ roleName: "generally awesome person" }
]
}
};
// this class is responsible for loading the data
// and making it available to other components.
// we'll create a singleton for this example, but
// it might make sense to have more than one instance
// for other use cases.
class UserStore {
constructor() {
// kick off the data load upon instantiation
this.load();
}
// statically available singleton instance.
// not accessed outside the UserStore class itself
static instance = new this();
// UserStore.connect creates a higher-order component
// that provides a 'store' prop and automatically updates
// the connected component when the store changes. in this
// example the only change occurs when the data loads, but
// it could be extended for other uses.
static connect = function(Component) {
// get the UserStore instance to pass as a prop
const store = this.instance;
// return a new higher-order component that wraps the connected one.
return class Connected extends React.Component {
// when the store changes just force a re-render of the component
onStoreChange = () => this.forceUpdate();
// listen for store changes on mount
componentWillMount = () => store.listen(this.onStoreChange);
// stop listening for store changes when we unmount
componentWillUnmount = () => store.unlisten(this.onStoreChange);
render() {
// render the connected component with an additional 'store' prop
return React.createElement(Component, { store });
}
};
};
// The following listen, unlisten, and onChange methods would
// normally be achieved by having UserStore extend EventEmitter
// instead of re-inventing it, but I wasn't sure whether EventEmitter
// would be available to you given your build restrictions.
// Adds a listener function to be invoked when the store changes.
// Called by componentWillMount for connected components so they
// get updated when data loads, etc.
// The store just keeps a simple array of listener functions. This
// method creates the array if it doesn't already exist, and
// adds the new function (fn) to the array.
listen = fn => (this.listeners = [...(this.listeners || []), fn]);
// Remove a listener; the inverse of listen.
// Invoked by componentWillUnmount to disconnect from the store and
// stop receiving change notifications. We don't want to attempt to
// update unmounted components.
unlisten = fn => {
// get this.listeners
const { listeners = [] } = this;
// delete the specified function from the array.
// array.splice modifies the original array so we don't
// need to reassign it to this.listeners or anything.
listeners.splice(listeners.indexOf(fn), 1);
};
// Invoke all the listener functions when the store changes.
// (onChange is invoked by the load method below)
onChange = () => (this.listeners || []).forEach(fn => fn());
// do whatever data loading you need to do here, then
// invoke this.onChange to update connected components.
async load() {
// the loading and loaded fields aren't used by the connected
// components in this example. just including them as food
// for thought. components could rely on these explicit fields
// for store status instead of pivoting on the presence of the
// data.user object, which is what the User and Role components
// are doing (below) in this example.
this.loaded = false;
this.loading = true;
try {
// faking the data request. wait two seconds and return our
// hard-coded data from above.
// (Replace this with your network fetch.)
this.data = await new Promise(fulfill =>
setTimeout(() => fulfill(fakeResponse), 2000)
);
// update the loading/loaded status fields
this.loaded = true;
this.loading = false;
// call onChange to trigger component updates.
this.onChange();
} catch (e) {
// If something blows up during the network request,
// make the error available to connected components
// as store.error so they can display an error message
// or a retry button or whatever.
this.error = e;
}
}
}
// With all the loading logic in the store, we can
// use a much simpler function component to render
// the user's name.
// (This component gets connected to the store in the
// React.createElement call below.)
function User({ store }) {
const { data: { user } = {} } = store || {};
return React.createElement(
"span",
{ className: "mr-2 d-none d-lg-inline text-gray-600 small" },
user ? user.fullName : "loading (User)…"
);
}
ReactDOM.render(
// Connect the User component to the store via UserStore.connect(User)
React.createElement(UserStore.connect(User), {}, null),
document.getElementById("userDropdown")
);
// Again, with all the data loading in the store, we can
// use a much simpler functional component to render the
// roles. (You may still need a class if you need it to do
// other stuff, but this is all we need for this example.)
function Roles({ store }) {
// get the info from the store prop
const { data: { user } = {}, loaded, loading, error } = store || {};
// handle store errors
if (error) {
return React.createElement("div", null, "oh noes!");
}
// store not loaded yet?
if (!loaded || loading) {
return React.createElement("div", null, "loading (Roles)…");
}
// if we made it this far, we have user data. do your thing.
const roles = user.roles.map(rol => rol.roleName);
return React.createElement(
"a",
{ className: "dropdown-item" },
roles.join(", ")
);
}
ReactDOM.render(
// connect the Roles component to the store like before
React.createElement(UserStore.connect(Roles), {}, null),
document.getElementById("dropdownRol")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="userDropdown"></div>
<div id="dropdownRol"></div>

unable to select all checkboxes in tree using angular2-tree on init

Objective : i have a button named "feed data" so when ever i click it the data will be loaded i mean the tree with checkboxes here my requirement is when ever i click it along with data all the check boxes have to be checked on init i tried using
this.treeComp.treeModel.doForAll((node: TreeNode) => node.setIsSelected(true));
but it is not working below is my code
click(tree: TreeModel) {
this.arrayData = [];
let result: any = {};
let rs = [];
console.log(tree.selectedLeafNodeIds);
Object.keys(tree.selectedLeafNodeIds).forEach(x => {
let node: TreeNode = tree.getNodeById(x);
// console.log(node);
if (node.isSelected) {
if (node.parent.data.name) //if the node has parent
{
rs.push(node.parent.data.name + '.' + node.data.name);
if (!result[node.parent.data.name]) //If the parent is not in the object
result[node.parent.data.name] = {} //create
result[node.parent.data.name][node.data.name] = true;
}
else {
if (!result[node.data.name]) //If the node is not in the object
result[node.data.name] = {} //create
rs.push(node.data.name);
}
}
})
this.arrayData = rs;
tree.selectedLeafNodeIds = {};
}
selectAllNodes() {
this.treeComp.treeModel.doForAll((node: TreeNode) => node.setIsSelected(true));
// firstNode.setIsSelected(true);
}
onTreeLoad(){
console.log('tree');
}
feedData() {
const results = Object.keys(this.data.info).map(k => ({
name: k,
children: this.data.info[k].properties
? Object.keys(this.data.info[k].properties).map(kk => ({ name: kk }))
: []
}));
this.nodes = results;
}
feedAnother() {
const results = Object.keys(this.dataa.info).map(k => ({
name: k,
children: this.dataa.info[k].properties
? Object.keys(this.dataa.info[k].properties).map(kk => ({ name: kk }))
: []
}));
this.nodes = results;
}
onActivate(event) {
this.selectedDataList.push(event.node.data);
console.log(this.selectedDataList)
}
onDeactivate(event) {
const index = this.selectedDataList.indexOf(event.node.data);
this.selectedDataList.splice(index, 1);
console.log(this.selectedDataList)
}
below is my stackblitz https://stackblitz.com/edit/angular-hrbppy
Use updatedata and initialized event to update the tree view to check all checkboxes.
app.component.html
<tree-root #tree *ngIf ="nodes" [nodes]="nodes" [options]="options" [focused]="true"
(initialized)="onTreeLoad()"
(updateData)="updateData()"
(select)="onActivate($event)"
(deselect)="onDeactivate($event)">
</tree-root>
It'll initiate tree-root component only if nodes variable is available,
then in the initialized and updateData event call selectAllNodes method to select all checkboxes.
app.component.ts
updateData() {
this.selectAllNodes();
}
onTreeLoad(){
this.selectAllNodes();
}
Refer to this slackblitz for working example.
just, in your function feed data call to your function this.selectAllNodes() enclosed in a setTimeout. You can see your forked stackblitz
setTimeout(()=>{
this.selectAllNodes()
})
NOTE: I see in your code you try to control in diferents ways the items selected. I simplified using a recursive function.
In this.treeComp.treeModel.selectedLeafNodeIds we have the items that are changed, so
getAllChecked()
{
const itemsChecked=this.getData(
this.treeComp.treeModel.selectedLeafNodeIds,null)
console.log(itemsChecked);
}
getData(nodesChanged,nodes) {
nodes=nodes||this.treeComp.treeModel.nodes
let data: any[] = []
nodes.forEach((node: any) => {
//in nodesChanged we has object like {1200002:true,123132321:false...}
if (nodesChanged[node.id]) //can be not changed, and then it's null because
//it's not in object or can be changed to false
data.push({id:node.id,name:node.name})
//or data.push(node.name); //if only need the "name"
if (node.children)
data=[...data,...this.getData(nodesChanged,node.children)]
}
);
return data
}
Updated I updated the function getData to include the "parent" of the node, but looking the code of #Raghul selvam, his function like me more than mine.
getData(nodesChanged,nodes,prefix) {
nodes=nodes||this.treeComp.treeModel.nodes
let data: any[] = []
nodes.forEach((node: any) => {
if (nodesChanged[node.id])
data.push(prefix?prefix+"."+node.name:node.name)
if (node.children)
data=[...data,...this.getData(nodesChanged,node.children,prefix?prefix+"."+node.name:node.name)]
}
);
return data
}
And call it as
this.getData(this.treeComp.treeModel.selectedLeafNodeIds,null,"")
You could add this in your onTreeLoad function. You could add a boolean flag(treeLoaded) for tracking if the tree has loaded or not.
onTreeLoad(tree){
this.selectAllNodes();
this.treeLoaded = true;
}

Angular service returning data only on page refresh

Why is this.menulist empty even after?
layout.component.ts
ngOnInit() {
this.menulist = localStorage.getItem('menulist');
if (this.menulist) {
} else {
this.SetMenuList(); // Else call the service and sort the menu then
}
this.menulist = localStorage.getItem('menulist'); // null even after calling SetMenulist
this.jsonmenulist = JSON.parse(this.menulist);
}
SetMenuList() {
this._UserspecificmenuaccessService.getRMA("driver")
.map((lst) => {
if (lst && lst.length > 0) {
console.log(lst);
localStorage.setItem('menulist', JSON.stringify(lst));
this.menulist = localStorage.getItem('menulist'); this.jsonmenulist = JSON.parse(this.menulist);
}
}, (error) => {
console.error(error)
});
}
Service method:
getRMA(Id:any): Observable<Imstmenu[]> {
return this._http.get("http://172.19.32.235:3000/api/USMA/selectAll/"+Id+"")
.map((response: Response) => {<Imstmenu[]>response.json();
.catch(this.handleError);
}
I suggest creating an Observable that gets the last value from local if available, else the server value after updating localstorage :
updateMenuListOnce$ is an Observable that will call the server, updates localstorage and return the new value :
const updateMenuListOnce$ =
this._UserspecificmenuaccessService.getRMA("driver")
.pipe(first(),
tap(menuList => localStorage.setItem('menulist', JSON.stringify(menuList)));
The menuList$ Observable will return the value in localstorage if available, or subscribe to the previous declared Observable :
this.menuList$ = of(localStorage.getItem('menulist'))
.pipe(exhaustMap(menuList =>
menuList ? of(<Imstmenu[]>menuList) : updateMenuListOnce$));
Now you just need to subscribe to the menuList$ to get the menu list from localstorage if available or from the server.

Angular2 how do they save to cache?

I was trying out the 5 min Anuglar2 Tutorial and when it said that you are able to use external templates I tried it.
My component looks like this
import {Component, Template, bootstrap} from 'angular2/angular2';
// Annotation section
#Component({
selector: 'my-app'
})
#Template({
url: "component.html"
})
// Component controller
class MyAppComponent {
constructor() {
this.name = 'Alice';
}
}
bootstrap(MyAppComponent);
I had a mistake in my external template and fixed it, but the HTML file was still cached so I couldn't the effects in the browser.
Figuring out how they cache it I looked at the code on Github
I found this
#angular/modules/angular2/src/core/compiler/template_loader.js
#Injectable()
export class TemplateLoader {
_xhr: XHR;
_htmlCache: StringMap;
_baseUrls: Map<Type, string>;
_urlCache: Map<Type, string>;
_urlResolver: UrlResolver;
constructor(xhr: XHR, urlResolver: UrlResolver) {
this._xhr = xhr;
this._urlResolver = urlResolver;
this._htmlCache = StringMapWrapper.create();
this._baseUrls = MapWrapper.create();
this._urlCache = MapWrapper.create();
}
// TODO(vicb): union type: return an Element or a Promise<Element>
load(template: Template) {
if (isPresent(template.inline)) {
return DOM.createTemplate(template.inline);
}
if (isPresent(template.url)) {
var url = this.getTemplateUrl(template);
var promise = StringMapWrapper.get(this._htmlCache, url);
if (isBlank(promise)) {
promise = this._xhr.get(url).then(function (html) {
var template = DOM.createTemplate(html);
return template;
});
StringMapWrapper.set(this._htmlCache, url, promise);
}
return promise;
}
So I check out the StringMapWrapper angular/modules/angular2/src/facade/collection.es6
and for set the code is just
static set(map, key, value) {
map[key] = value;
}
I saw that the StringMapWrapper comes from global
export var StringMap = global.Object;
But looking in angular/modules/angular2/src/facade/lang.es6 I cant figure out where the Map is cached.
I do not know much about the caching process and hope someone could explain how they do it in this case.
StringMapWrapper.create() creates an object literal {}. They use something like StringMapWrapper for Dart support where these primitives are created differently in the other language. In short all they're doing is this
var cache = {};
xhr(templateUrl).then(template => {
cache[templateUrl] = template;
return template;
})
#gdi2290 had nearly answered your question, and if you want to understand more about Cache Management in JavaScript/TypeScript, please see my post here http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html .
There is a step by step explanation of a cache management class which acts as Layer to AJAX, and can be injected to components as Service. Here's a synopsis of code from class:-
private loadPostCache(link:string){
if(!this.loading[link]){
this.loading[link]=true;
this.links[link].forEach(a=>this.dataObserver[a].next(false));
this.http.get(link)
.map(this.setValue)
.catch(this.handleError).subscribe(
values => {
this.data[link] = values;
delete this.loading[link];
this.links[link].forEach(a=>this.dataObserver[a].next(false));
},
error => {
delete this.loading[link];
}
);
}
}
private setValue(res: Response) {
return res.json() || { };
}
private handleError (error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
postCache(link:string): Observable<Object>{
return Observable.create(observer=> {
if(this.data.hasOwnProperty(link)){
observer.next(this.data[link]);
}
else{
let _observable=Observable.create(_observer=>{
this.counter=this.counter+1;
this.dataObserver[this.counter]=_observer;
this.links.hasOwnProperty(link)?this.links[link].push(this.counter):(this.links[link]=[this.counter]);
_observer.next(false);
});
this.loadPostCache(link);
_observable.subscribe(status=>{
if(status){
observer.next(this.data[link]);
}
}
);
}
});
}

Categories