Performance: Why does AngularJS evaluate watch when scope model hasn't changed? - performance

So I have a watch on scope A. Why does AngularJS evaluate it when a local variable on sibling scope B changes? The data model of scope A hasn't changed.
Here is a minimal example of this:
there is a custom watch on scope A.
the input element is bound to the text variable of scope B.
Note that the text is not shown, because it's not visible from scope A.
Controllers:
function Ctrl1($scope) {
console.log($scope); // first scope
// my custom watch expression
var count = 0;
$scope.$watch(function() {
count++;
console.log("call count: " + count);
}, function() {
// the listener does nothing
// I'm just interested in when the watch expression is called
});
}
function Ctrl2($scope) {
console.log($scope); // second scope
}
HTML:
<div ng-app>
<div ng-controller="Ctrl1">
{{text}}
</div>
<div ng-controller="Ctrl2">
<input type="text" ng-model="text"></input>
</div>
</div>
(Feel free to try this code here: http://jsfiddle.net/s3Wz5/4/ )
If you type some text in the input element (scope B), the custom watch of the scope A is evaluated. Why is this? Why doesn't AngularJS know that there are no changes to the data model of scope A whatsoever?
Update: Some clarification:
I don't want to watch text. This is a performance related question, wondering why the watch is evaluated even though text cannot possibly be read from the other scope!

Because $watch is evaluated in every digest cycle. Change value of a ng-model triggers digest cycle. This behavior is different from $scope.$digest(), which only triggers digestion on current and child scopes.
I think ng-model might NOT use $scope.$digest() internally. It might use some other mechanism to trigger digest after value changes.

Related

Mixing Alpine.js with 'static' serverside markup, while getting the benefits of binding, etc

I'm new to Alpine and struggling to wrap my head around how to make a scenario like this work:
Let's say I have a serverside built page, that contains some buttons, that represent newsletters, the user can sign up to.
The user might have signed up to some, and we need to indicate that as well, by adding a css-class, .i.e is-signed-up.
The initial serverside markup could be something like this:
<button id='newsletter-1' class='newsletter-signup'>Newsletter 1</button>
<div>some content here...</div>
<button id='newsletter-2' class='newsletter-signup'>Newsletter 2</button>
<div>more content here...</div>
<button id='newsletter-3' class='newsletter-signup'>Newsletter 3</button>
<div>and here...</div>
<button id='newsletter-4' class='newsletter-signup'>Newsletter 4</button>
(When all has loaded, the <button>'s should later allow the user to subscribe or unsubscribe to a newsletter directly, by clicking on one of the buttons, which should toggle the is-signed-up css-class accordingly.)
Anyway, then I fetch some json from an endpoint, that could look like this:
{"newsletters":[
{"newsletter":"newsletter-1"},
{"newsletter":"newsletter-2"},
{"newsletter":"newsletter-4"}
]}
I guess it could look something like this also:
{"newsletters":["newsletter-1", "newsletter-2", "newsletter-4"]}
Or some other structure, but the situation would be, that the user have signed up to newsletter 1, 2 and 4, but not newsletter 3, and we don't know that, until we get the JSON from the endpoint.
(But maybe the first variation is easier to map to a model, I guess...)
Anyway, I would like to do three things:
Make Alpine get the relation between the model and the dom elements with the specific newsletter id (i.e. 'newsletter-2') - even if that exact id doesn't exist in the model.
If the user has signed up to a newsletter, add the is-signed-up css-class to the corresponding <button> to show its status to the user.
Bind to each newsletter-button, so all of them – not just the ones, the user has signed up to – listens for a 'click' and update the model accordingly.
I have a notion, that I might need to 'prepare' each newsletter-button beforehand with some Alpine-attributes, like 'x-model='newsletter-2', but I'm still unsure how to bind them together when Alpine has initialising, and I have the data from the endpoint,
How do I go about something like this?
Many thanks in advance! 😊
So our basic task here is to add/remove a specific item to/from a list on a button click. Here I defined two component: the newsletter component using Alpine.data() creates the data (subs array), provides the toggling method (toggle_subscription(which)) and the checking method (is_subscribed(which)) that we can use to set the correct CSS class to a button. It also handles the data fetching in the init() method that executes automatically after the component is initialized. I have also created a save method that we can use to send the subscription list back to the backend.
The second component, subButton with Alpine.bind() is just to make the HTML code more compact and readable. (We can put each attribute from this directly to the buttons.) So on click event it calls the toggle_subscription with the current newsletter's key as the argument to add/remove it. Additionally it binds the bg-red CSS class to the button if the current newsletter is in the list. For that we use the is_subscribed method defined in our main component.
.bg-red {
background-color: Tomato;
}
<script src="https://unpkg.com/alpinejs#3.x.x/dist/cdn.min.js" defer></script>
<div x-data="newsletter">
<button x-bind="subButton('newsletter-1')">Newsletter 1</button>
<button x-bind="subButton('newsletter-2')">Newsletter 2</button>
<button x-bind="subButton('newsletter-3')">Newsletter 3</button>
<button x-bind="subButton('newsletter-4')">Newsletter 4</button>
<div>
<button #click="save">Save</button>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('newsletter', () => ({
subs: [],
init() {
// Fetch list of subscribed newsletters from backend
this.subs = ['newsletter-1', 'newsletter-2', 'newsletter-4']
},
toggle_subscription(which) {
if (this.subs.includes(which)) {
this.subs = this.subs.filter(item => item !== which)
}
else {
this.subs.push(which)
}
},
is_subscribed(which) {
return this.subs.includes(which)
},
save() {
// Send this.sub to the backend to save active state.
}
}))
Alpine.bind('subButton', (key) => ({
'#click'() {
this.toggle_subscription(key)
},
':class'() {
return this.is_subscribed(key) && 'bg-red'
}
}))
})
</script>

SharepointFramework - how to set the actual webpart code as initial value in PropertyFieldCodeEditor

Hello i am using this custom property pane control called PropertyFieldCodeEditor. what i want is to display the actual webpart code as the initial value of the code editor, then after i click save, the changes will be reflected on the webpart..
this is the code of PropertyFieldCodeEditor
PropertyFieldCodeEditor('htmlCode', {
label: 'Edit Code',
panelTitle: 'Edit Code',
initialValue: "",
onPropertyChange: this.onPropertyPaneFieldChanged,
properties: this.properties,
disabled: false,
key: 'codeEditorFieldId',
language: PropertyFieldCodeEditorLanguages.HTML
})
i tried to put this.domElement on initialvalue but it only accept string, also i cand find a way to convert this.domelement to string..
also what should i put inside
protected onPropertyPaneFieldChanged(path: string, newValue: string) {}
For initialValue, you should be able to use this.domElement.innerHTML or this.domElement.outerHTML. Both are strings representing the contents of domElement (note, domElement is really just an HTMLElement).
outerHTML will include everything, including one extra div layer on the outside:
<div style="width: 100%;">
<div class="helloWorld_d3285de8">
...
</div>
</div>
innerHTML is only the inside contents of that div:
<div class="helloWorld_d3285de8">
...
</div>
You'll probably want innerHTML, since that's what's initially used in the render method.
Once you set the initialValue, you would have accomplished copying your web part code into the PropertyFieldCodeEditor. Now you would need to get the PropertyFieldCodeEditor contents (which is stored in your property htmlCode) assigned back into this.domElement.innerHTML.
Unfortunately, in onPropertyPaneFieldChanged, this points to the PropertyFieldCodeEditor, not to the web part class anymore. You may or may not be able to do it here - I didn't look too deeply into it.
The easiest solution I figured was, in render, to assign this.domElement.innerHTML like so:
public render(): void {
this.domElement.innerHTML = this.properties.htmlCode || `
<div class="${styles.helloWorld}">
...
</div>`;
}
This way, the web part will initially render whatever comes after the ||. But as soon as you save a change to the PropertyFieldCodeEditor, it will start rendering the htmlCode instead. This only works because initially htmlCode is undefined. (Note it won't work exactly like this if you assign something truthy to it via your web part's preconfiguredEntries - you would have to write some further checks. The principle is the same, though.)

Ember. How to delete a view's instance

Im struggling to delete a view's instance. On view hbs i use each loop to show view hbs. On another field click i add a " . " to a json object, which then adds another field to the template.
>js>App.ApplicationView = Ember.View.extend({
anotherField: [{name: 'testname'}],
actions: {
moreFields: function(){
this.get('anotherField').pushObject({name: ''});
},
less: function(){
var counter = this.get('anotherField');
counter.shift();
this.set('anotherField', counter);
And hbs
{{#each view.anotherField}}
{{view Ember.TextField}}
{{/each}}
<button {{action 'moreFields' target='view'}}> ++ </button>
<button {{action 'less' target='view'}}> -- </button>
http://jsbin.com/iSUdiCaX/17/edit
Cheers
Kristjan
When you use the shift method Ember doesn't get notified that the anotherField property changed, and therefore it doesn't update the template. You can check this by adding this.rerender() at the end of the less action.
You could:
call this.propertyDidChange('anotherField') to notify the property changed: http://emberjs.com/api/classes/Ember.Object.html#method_propertyDidChange
use the slice method: http://emberjs.com/api/classes/Ember.Array.html#method_slice
var sliced = this.get('anotherField').slice(0, this.get('anotherField').length - 1);
this.set('anotherField' sliced);
I also noticed you're using the View to handle the actions whereas I believe the Controller would be a better place to do so.
EDIT
Well it depends.... I believe the controllers are a good place because they have knowledge of the model (the view also has it via the controller). if your anotherField property is only needed for displaying or event handling logic then I believe it is a good idea to leave it in the view. from docs
Views in Ember.js are typically only created for the following
reasons:
When you need sophisticated handling of user events
When you want to create a re-usable component
But if instead the anotherField property is used the held application state (user selections, needed for computed properties or other actions) then I believe it's better placed inside the controller (and therefore the actions modifying it).
Have in mind your view can handle one part of the action and send it to the controller:
actions: {
something: function() {
.....
this.get('controller').send('something') // calls send action in controller
}
}
I hope this helps!

Form select box in Backbone Marionette

I'm trying using Backbone.Marionette to build an application. The application gets its data through REST calls.
In this application I created a model which contains the following fields:
id
name
language
type
I also created an ItemView that contains a complete form for the model. The template I'm using is this:
<form>
<input id="model-id" class="uneditable-input" name="id" type="text" value="{{id}}"/>
<input id="model-name" class="uneditable-input" name="name" type="text" value="{{name}}" />
<select id="model-language" name="language"></select>
<select id="model-type" name="type"></select>
<button class="btn btn-submit">Save</button>
</form>
(I'm using Twig.js for rendering the templates)
I am able to succesfully fetch a model's data and display the view.
What I want to do now is populate the select boxes for model-language and model-type with options. Language and type fields are to be restricted to values as a result from REST calls as well, i.e. I have a list of languages and a list of types provided to me through REST.
I'm contemplating on having two collections, one for language and one for type, create a view for each (i.e. viewLanguageSelectOptions and viewTypeSelectOptions), which renders the options in the form of the template I specified above. What I am not sure of is if this is possible, or where to do the populating of options and how to set the selected option based on data from the model. It's not clear to me, even by looking at examples and docs available, which Marionette view type this may best be realized with. Maybe I'm looking in the wrong direction.
In other words, I'm stuck right now and I'm wondering of any of you fellow Backbone Marionette users have suggestions or solutions. Hope you can help!
Create a view for a Select in my opinion is not needed in the scenario that you are describing, as Im assuming that your languages list will not be changing often, and the only porpouse is to provide a list from where to pick a value so you can populate your selects in the onRender or initializace function of your view using jquery.
you can make the calls to your REST service and get the lists before rendering your view and pass this list to the view as options and populate your selects on the onRender function
var MyItemView = Backbone.Marionette.ItemView.extend({
initialize : function (options) {
this.languages = options.languages;
this.typeList = options.typeList;
},
template : "#atemplate",
onRender : function () {
this.renderSelect(this.languages, "#languagesSelect", "valueofThelist");
this.renderSelect(this.typeList, "#typesSelect", "valueofThelist")
},
renderSelect :function (list, element, value) {
$.each(list, function(){
_this.$el.find(element).append("<option value='"+this[value]+"'>"+this[value]+"</option>");
});
}
})
var languagesList = getLanguages();
var typeList = getTypesList();
var myItemView = new MyItemView({languages:languagesList,typeList :typeList });
Hope this helps.

Failure to trap dijit.Tree "onclick"

I've read several examples of handling onClick for dijit.Tree.. in particular this one seems to tell me all I need: dojo how to override dijit class method
However, for some reason my handler gets called when my page first loads, and never when I click on a tree node?
Here's the code:
<div dojoType="dijit.layout.ContentPane" title="Published Blueprints" minSize="20" style="width: 300px;" id="leftAccordion" region="leading" splitter="true">
<div id="blueprintTree" dojoType="dijit.Tree" store="" query="" label="Blueprints" openOnClick="false">
</div>
</div>
...and I then do this...
dojo.ready(function() {
var tree = dijit.byId("blueprintTree");
tree.connect(tree, "onClick", function(item) {
// my code here...
});
});
... the "my code here" part gets invoked when I start (in debug) my jsp, but never when i lock around on nodes...
Obviously I'm missing something simple?
Regards
Brian
Is it required to put the connect inside the dojo.ready()? Maybe that is why it is called on startup?
Looking at the dijit.Tree source, I saw that the onClick had two args
This is what I used in my case to successfully capture onClicks:
In the Tree constructor add openOnClick: false:
var tree = new dijit.Tree( {
model: myModel,
openOnClick: false,
etc...
Then in the same function where I create the tree using the programmatic approach
dojo.connect( tree,"onClick", function(/*dojo.data*/ item, /*TreeNode*/ nodeWidget){
//my code
});

Resources