If I have a list of todos bund to an observable and then change one of the todos will all items then be rerendered?
In Angular V2 we have 2 types of Components Stateful(Smart) and Statless. Means(Dumb)
Stateful: Are concerned with how things work.
Statless: Are concerned with how things look.
ChangeDetectionStrategy:
When you don't defined any strategy to your component, the ChangeDetection will always "rerun" this component.
In Case of Strategy OnPush he only updates himself if any of inputs changes, but that case only works for Immutables like String, Number not for Objects, or Arrays.
So with ngrx/store and ES6 you can create Immutable Arrays (not the only way)
like
case ADD_TODO:
return [...state, action.payload];
case UPDATE_TODO:
return state.map(todo => todo.id === action.payload.id ? action.payload : todo);
Then you will create to components todos and todo
<todos [todos]="todosNgRx$ | async"></todos>
This component will get the list of todos and re render the second component the Todo.
<todo *ngFor="let todo of todos" [todo] = "todo"></todo>
This component is dumb, only presents the data.
You need to give ChangeDetectionStrategy.OnPush so only updates in case of a todo is updated. And if you add a new todo the ngFor will an new component Todo and the rest of the list will not be updated.
Hope it helps
I found a todo plunker created with angular 2 and ngrx/store.
It's easy to see in your webbrowsers devtools that only altered items get rendered. So in that regard angular and react are the same.
"plnkr.co/edit/Hb4pJP3jGtOp6b7JubzS?p=preview"
Related
I'm having issue getting the React Fabric UI DetailsList to work with React Hooks. It renders, but the selection part does not. Whenever, you select a row, I expect the count of the selection to be updated. However, i'm not seeing that. It looks like the selection component items never get updated even thou the UI shows it being selected. I'm also seeing the onSelectionChange being triggered when you select a row. Now sure if its because i'm using react hooks or what
I took the provided class example which works:
[Codepen]https://codepen.io/pen/?&editable=true (Example)
Same as the original but stripped down
[Codepen]https://codepen.io/darewreckk/pen/NQRWEd?editors=1111
converted it to a react hook component. I would expect the same thing, but i'm not and can't figure out what's different.
Modified Example with Hooks
[Codepen]https://codepen.io/darewreckk/pen/GVjRNb?editors=1111
Any advice appreciated,
Thanks,
Derek
The selection count is stored on the Selection object that you are creating. You could log it out like so:
const _selection = new Selection({
onSelectionChanged: () => {
console.log('Selected count:' + _selection.count);
}
});
You can set the value of selectionCount and selectionDetails whenever onSelectionChanged is called:
const _selection = new Selection({
onSelectionChanged: () => {
setSelectionCount(_selection.count);
setSelectionDetails(`${_selection.count} items selected`)
}
});
As a side note: If you want selectionDetails to update when selectionCount updates, you can use the useEffect hook:
React.useEffect(() => {
setSelectionDetails(`${selectionCount} items selected`);
}, [selectionCount]);
The above effect will run whenever selectionCount updates, and so in turn update selectionDetails.
It seems Aurelia is not aware when I create and append an element in javascript and set a custom attribute (unless I am doing something wrong). For example,
const e = document.createElement('div');
e.setAttribute('custom-attr', 'some value');
body.appendChild(e);
Is there a way to make Aurelia aware of this custom attribute when it gets appended?
A little background: I am creating an app where the user can select their element type (e.g. input, select, checkbox etc.) and drag it around (the dragging is done in the custom attribute). I thought about creating a wrapper <div custom-attr repeat.for="e of elements"></div> and somehow render the elements array, but this seemed inefficient since the repeater will go through all the elements everytime I push a new one and I didn't not want to create a wrapper around something as simple as a text input that might be created.
You would have to manually trigger the Aurelia's enhance method for it to register the custom attributes or anything Aurelia related really. And you also have to pass in a ViewResources object containing the custom attribute.
Since this isn't as straight forward as you might think, I'll explain it a bit.
The enhance method requires the following parameters for this scenario:
Your HTML as plain text (string)
The binding context (in our scenario, it's just this)
A ViewResources object that has the required custom attribute
One way to get access to the ViewResources object that meets our requirements, is to require the custom attribute into your parent view and then use the parent view's ViewResources. To do that, require the view inside the parent view's HTML and then implement the created(owningView, thisView) callback in the controller. When it's fired, thisView will have a resources property, which is a ViewResources object that contains the require-d custom attribute.
Since I am HORRIBLE at explaining, please look into the example provided below.
Here is an example how to:
app.js
import { TemplatingEngine } from 'aurelia-framework';
export class App {
static inject = [TemplatingEngine];
message = 'Hello World!';
constructor(templatingEngine, viewResources) {
this._templatingEngine = templatingEngine;
}
created(owningView, thisView) {
this._viewResources = thisView.resources;
}
bind() {
this.createEnhanceAppend();
}
createEnhanceAppend() {
const span = document.createElement('span');
span.innerHTML = "<h5 example.bind=\"message\"></h5>";
this._templatingEngine.enhance({ element: span, bindingContext: this, resources: this._viewResources });
this.view.appendChild(span);
}
}
app.html
<template>
<require from="./example-custom-attribute"></require>
<div ref="view"></div>
</template>
Gist.run:
https://gist.run/?id=7b80d2498ed17bcb88f17b17c6f73fb9
Additional resources
Dwayne Charrington has written an excellent tutorial on this topic:
https://ilikekillnerds.com/2016/01/enhancing-at-will-using-aurelias-templating-engine-enhance-api/
I wasn't sure of the best way to word the title, but hopefully the description will clarify.
I have an Angular2 component that uses Redux for state management.
That component uses *ngFor to render an array of small inputs with buttons like this. The "state" is something like this...
// glossing over how I'd get this from the Redux store,
// but assume we have an Immutable.js List of values, like this...
let items = Immutable.fromJS([{value: foo}, {value: bar}, /*...etc*/ })
And the template renders that like so...
<input *ngFor="let item of items, let i = index"
type="text"
value="item.get('value')"
(blur)="onBlur($event, i)">
<button (click)="onClick($event)">Add New Input</button>
When an input is focused and edited, then focus is moved away, the onBlur($event) callback is called, a redux action (ie: "UPDATE_VALUE") is dispatched with the new value.
onBlur($event, index) {
let newValue = $event.target.value;
this.ngRedux.dispatch({
type: "UPDATE_VALUE",
value: {index, newValue}
});
}
And the reducer updates the value (using Immutable.js):
case "UPDATE_VALUE": {
let index = getIndex(action.value.index); // <-- just a helper function to get the index of the current value.
return state.updateIn(["values", index], value => action.value.newValue);
}
The state is updated, so the component is re-rendered with the updated value.
When the button next to the input is clicked, the onClick($event) callback is fired which dispatches a different redux action (ie: "ADD_VALUE"), updates the state, and the component is re-rendered with a new blank input & button.
The problem comes up when the input is focused (editing) and the user clicks the button. The user intended to click the button, but because they happened to be focused on the field, it doesn't behave as expected. The (blur) event is fired first, so the input onBlur callback is fired, redux action dispatched, state updated, component re-rendered. BUT, the button (click) is lost, so the new input isn't created.
Question:
Is there a way to keep track of the click event and trigger the second action after the re-render? Or, is there a way to somehow chain the redux actions so they happen in sequence before the re-render?
Side-note - I've tried changing the (click) event to use (mousedown) which is triggered before the (blur) but that caused Angular (in devMode) to complain that the #inputs to the components were changed after being checked (the state changed during the change detection cycle). I didn't dig into that too deeply yet though. I'm hoping to find a solution using click and blur as is.
Thanks!
This is a general question. I have a redux saga yielding calls that update the store every x mins and show the store gets updated appropriately in the redux dev tool. In the render method of my component if I click before the data I will get a spinner and if I click after the component will render; HOWEVER, in the components class the life cycle "componentWillUpdate" or "componentWillReceiveProps" shows the connected piece in redux store as undefined in either method yet the render is able to pass the correct props; what the cluck? I'll head back to the docs but this seems odd.
...
//dont usually use this for redux
componentWillReceiveProps(){
console.log(dailyOperations) // nothing here
}
componentWillUpdate(){
console.log(dailyOperations) // nothing here
}
render(){
if (dailyOperations === undefined) {
return (<SpinnerThing />)
else
return (<SomeDisplayComponent data={dailyOperations} />) //Data is here
}
I couldn't at the time account for it but now it makes sense and if it helps anyone else great. It will not be there on the update for the connected redux state but through "componentWillUpdate(nextProps)" & nextProps will have it.
This is something I've been trying to do for quite some time. I've managed something, but I am pretty sure there is a better way.
Question
I want to be able to hook into the bindingProvider process to augment the name of the observable with a prefix (or something similar).
So if I have:
<button data-bind="text: label"></button>
I want to be able to intercept the processig of the binding and replace label with myLabel, so it essentially processed as:
<button data-bind="text: myLabel"></button>
and that is based on some data on the ViewModel that is being applied to the node.
Attempts
use preprocessNode and replace the label value with myLabel before KO gets to it
Obviously, I'd like to avoid doing that, especially since myLabel may be used only in some cases - since it's based on dynamic data on the ViewModel.
Also, no bindingContext reference is available, so I am not sure how to get to ViewModel.
Use custom bindingProvider and do some string/$data mash-up in getBindings
Use preprocess and augment the value
This would be the perfect candidate if not for 2 things: I will have to repeat that for all bindings, since the form is ko.bindingHandlers.<name>.preprocess and it doesn't give me bindingContext, so I can't use that ViewModel data :)
Use extenders
The problem with this is that I need that augmentation behaviour to be applied to all observable values, not just specific ones. By default.
Any suggestions?
Thank you.
Example
To further illustrate the requirement - imagine that I have a template that looks like this:
<ul data-bind="foreach: people">
<li>
<span data-bind="text: name"></span>,
<span data-bind="text: age"></span>
</li>
</ul>
Trivial. Now imagine that ViewModel data looks like this:
{
people: [
{name: 'John', age: 30},
{name: 'Dean', age: 40},
{age: 0.2}
]
}
Basically, there are people and some of them are just born and don't have a name yet.
I'd like to be able to return something like 'noname' for those who are nameless and under the age of 1.
I clearly can do it by changing the template, but this is something I do not want to do. The template may be reused for something that prints noname based on gender, rather than age or something similar.
Hope that helps.
An alternative approach that would solve this particular use case is a custom binding which takes the property name as a string and sets the text to the given property using the viewModel parameter to the binding handler.
A binding like that could look like this:
ko.bindingHandlers.customText = {
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var field = ko.unwrap(valueAccessor())
element.innerText = ko.unwrap(viewModel[field]);
}
}
Here's a full jsfiddle example.
You could also do something more sophisticated than a straight property name lookup - you could even pass in a function to the binding which takes the view model as a parameter and returns the relevant data.
So, observable are the way to go, thank you all who suggested it.
Eventually one of my colleagues had an idea that turned out to be a very nifty simple trick (and I was kicking myself for blacking out on this).
The idea is based on creating a label computed for mylabel observable. This would work in most cases, aside from the case when ViewModel already has an unrelated label observable, which will then be overridden by the computed - something that may not be desired.
For the ViewModel observable I want to map to (in the above example myLabel), instead of the original label you create a computed with the name of label (so that template still works as before), but a trick is to add the label observable not to the ViewModel itself, rather to an object that is instantiated with ViewModel as prototype.
Tha way - all the rest of elements to which this ViewModel is applied will use the label value that it has, and for this specific template the new label computed will shadow the "old" one - which is the desired effect.
Basically:
var Context = function() {
// create computed observable `label` for observable `myLabel`
// based on whatever ViewModel data is needed.
};
Context.prototype = ViewModel;
var context = new Context();
ko.applyBindings(context, node);