0

I want switching my organisation's application from Knockout to Vue, and some concept are missing or I misunderstand vueJs philosophy. So I can't reproduce the same behavior.

From what I've read, VueJs is really focused on component.

Each component manage is own piece of DOM.

https://fr.vuejs.org/v2/guide/reactivity.html

https://fr.vuejs.org/v2/guide/list.html

... (i read most of this guide page's before starting to type in my keyboard :p)

This behavior is nice for simple component that need to be shared across but in my case it's a bit frustrating because I need some module to be linked (see at the end with my question about context)

I've read another great article (https://jes.al/2017/05/migrating-from-knockoutjs-to-vuejs/) about "knockout to Vuejs transition" but it lacks of so specific details.

For example : its explain that "subscribe" (in KO) can be replaced by "watch" (in Vue) but doesn't explain "when" we can apply these watchers (see my questions at the end)

Here is a very small code extraction that could summarize most of my specific needs.

Html

<div id="app">
 <div data-bind="text: title"></div>
 <i data-bind="visible: changeTracker.hasChanges">save your change dude !!</i>
 <div data-bind="with: model">
     <!-- the binging  "with" allow us to move context to model for the current node and prevent typing "model." each time (ex: model.prop1, modeld.pro2,) -->

     <label data-bind="text: name"></label>
     <input type="text" data-bind="value: firstName"/>

     <span data-bind="text: $parent.nameAndFirstName">
     <!-- "$parent" allow us to access vm context itself -->

     <input type="text" data-bind="value: city"/>
 </div> 
 <div>
    <button data-bind="click: changeTracker.undo, enabled: changeTracker.hasChanges"></button>
    <button data-bind="click: saveData, enabled: changeTracker.hasChanges"></button>
 </div>
</div>

Javascript

var changeTrackerVM_Factory = function(){
    var self = {};

    self.initialState = null; // no need to be ko.observable since it's used for internal manipulation only

    // flag that will tell the UI that modification exist
    self.hasChanges = ko.observable(false);

    self.init = function(modelToTrack){
        // some piece of code .... subscribe ... and so on
    }

    self.undo = function(){
         // some piece of code that revrt the object to it's initial state
    };

    return self;
};

var userVM_Factory = function(){
   var self = {};
   // data from-to database
   self.model = {
       id: "", //-- don't need to be observable since it's not rendered and not modifiable
       name: ko.observable(""),
       firstName: ko.observable(""),
       city: ko.observable("")
   };

   self.nameAndFirstName = ko.computed(function(){
     return self.model.name() + "-" + self.model.firstname();
   });

   // build the custom tracker module
   self.changeTracker = changeTrackerVM_Factory(); 

   self.init = function(){
       //some ajaxRequest
       $.ajax(....).then(function(data){
            // map recieved datas
            self.model.name(data.name);
            ......

            // apply custom observation AFTER data mapping
            self.model.city.subscribe(function(newvalue){
                 alert("yeaah !! YOU changed the city value");
            });

            // apply the "custom tracking module" used to show the state in the UI
            self.changeTracker.init(self.model);
       });
   }

   self.save = function(){
        // some piece of code to send to DATABASE
   }

   return self;
};

// initialise the relation between the UI and the VM
var userVM = userVM_Factory();
ko.applybinding(userVM , "#app");

If i want to convert this code to vueJS and i have the same behavior i need to answer :

  • how can i track only few properties of "model" (i'am sure it's possible since such an used libray would have been concerned by perf optimization)

  • how can i initiate "watch" ONLY AFTER first initialisation (and prevent the "alert" popup at the begining)

  • As you can see, i have not "one module for his DOM piece" ex: changeTracker.hasChanges is used at the top and at the bottom of the DOM

  • if i use component to manage "model" only with its own piece of DOM.. how can i use $parent inside ???

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • In Vue there are components, that “manage their piece of the DOM” (be it a little or a large piece). Components are reusable - multiple times on the same page. But if you want a component to represent the **same STATE across your app** (e.g. display the same values, manage the same underlying entity, no matter how many times you place it in your template and where), then **you need to use state management**. If your app is not so intricate, then an event bus will do, but I’d suggest reading on **Vuex** - the de facto state manager for Vue. – muka.gergely Apr 18 '19 at 17:23
  • ok i will investigate Vuex more... so it would me that to achieve the same job as i did in Knockout I have to use additional libraray (vue + vuex)... I am interested to learn new thing but i have to evaluated the cost of the transition first. – Guy Baillon Rafart Apr 19 '19 at 07:13
  • Your simplified example doesn’t require state management, but I suppose that your whole app (more views/pages, functions and components) need a tool for state management. That’s the case when you need Vuex. During development you won’t feel that it’s a separate tool. – muka.gergely Apr 19 '19 at 08:56
  • in my simplified example the module "changeTracker" compare the the initial state of the object and when a change occur in "tracked properties only" and then determine if we should draw the "save button"... you are saying that i can achieve the same thing without Vuex.. right? If so i will have to determine how i can build module (changeTracker) that depends on another module (model) wich track few properties only (no need to track "Id" since it won't change) – Guy Baillon Rafart Apr 19 '19 at 09:38

1 Answers1

3

You're asking a rather loaded question (or questions, I should say).. At a basic level, these answers should suffice - as well as the example provided, which covers many of your questions. Please let me know if you have any questions.

If you want to track certain properties, you can either use computed properties, or watch properties. Computed properties vs Watched properties

If you want to skip the first change in a watch property, you'll have to integrate some sort of logic, like setting a bool flag. More on that here

There are a couple different ways to use an "external" js file in a component - you could just import it and use the necessary functions, etc.. Or you could use a mixin. More on mixins here

Child components emit data to parent components, parent components pass data to children via props. The example below displays this.


EDIT: you are asking about mixins and how to add "third" party modules to a component.. This CodePen does produces the same result as the original I provided below, only that it uses a mixin. This demonstrates how you can do "whatever you want" in any component via a mixin.. (or I hope it does, at least)...


Example code:

[CodePen mirror]

[CodePen using mixin]


/*****************************/
/* User VM Factory Component */
/*****************************/
const userVmFactoryComponent = {
  name: "userVmFactoryComponent",
  template: "#userVmFactoryComponent",
  props: {
    id: [String, Number],
    name: String,
    lastName: String,
    city: String,
  },
  data() {
    return {
      user: {
        id: "",
        name: "",
        lastName: "",
        city: "",
      }
    }
  },
  methods: {
    emitNameChanged(){
      this.$emit('name-changed', this.fullName);
    }
  },
  computed: {
    fullName() { // using fullName vs nameAndFirstName
      return this.user.name + " " + this.user.lastName;
    }
  },
  mounted(){
    this.user.id = this.id;
    this.user.name = this.name;
    this.user.lastName = this.lastName;
    this.user.city = this.city;
  }
}

/****************/
/* Main Vue App */
/****************/
new Vue({
  el: "#app",
  components: {
    userVmFactoryComponent,
  },
  data: {
    users: [],
  },
  methods: {
    handleNameChange(info) {
      alert("You changed the name! (alerted from parent) " + "'" + info + "'")
    }
  },
  mounted() {
    // pretend you're getting this data from an API
    let firstUser = {
      id: 100,
      name: 'John',
      lastName: 'Smith',
      city: 'Paris',
    };
    
    let secondUser = {
      id: 200,
      name: "Jane",
      lastName: "Doe",
      city: "New York",
    }
    
    this.users.push(firstUser);
    this.users.push(secondUser);
  }
})
.user-input {
  margin-bottom: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>


<!-- MAIN VUE APP -->
<div id="app">
  <div>
  <div>
    <div>Without Looping</div>
    <user-vm-factory-component 
      @name-changed="handleNameChange"
      :id="users[0].id" 
      :name="users[0].name" 
      :last-name="users[0].lastName" 
      :city="users[0].city"
    ></user-vm-factory-component>
    <br/>
    <user-vm-factory-component 
      @name-changed="handleNameChange"
      :id="users[1].id" 
      :name="users[1].name" 
      :last-name="users[1].lastName" 
      :city="users[1].city"
    ></user-vm-factory-component>
  </div>
    <br/><hr/><br/>
    <div>
      <div>With Looping</div>
      <div>
        <div v-for="(user, index) in users">
          <user-vm-factory-component
            @name-changed="handleNameChange"
            :id="user.id"
            :name="user.name"
            :last-name="user.lastName"
            :city="user.city"
          ></user-vm-factory-component>
          <br/>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- ===================================== -->
<!-- THIS SIMULATES userVmFactoryComponent -->
<!-- ===================================== -->
<script type="text/x-template" id="userVmFactoryComponent">
  <div>
    Id
    <div class="user-input">
      <input type="text" v-model="user.id" />
    </div>
    Name (alerts when changed)
    <div class="user-input">
      <input type="text" @input="emitNameChanged" v-model="user.name" />
    </div>
    LastName
    <div class="user-input">
      <input type="text" v-model="user.lastName" />
    </div>
    FullName
    <div class="user-input">
      <input type="text" v-model="fullName" />
    </div>
    City
    <div class="user-input">
      <input type="text" v-model="user.city" />
    </div>
  </div>
</script>
tony19
  • 125,647
  • 18
  • 229
  • 307
Matt Oestreich
  • 8,219
  • 3
  • 16
  • 41
  • Thanks for the great example i will keep that in my magic box :). More seriously.. for the frist point, i already know computed and watch but what i wanted to say is that Vue seems to **track** ALL the stuff in the "data" object... whereas in knockout i specify what **i want to track** (ko.observable keyword)... so I was concerned about the perf (when data object is BIG)... for the second point, i should have think about it alone, glad you helped me... – Guy Baillon Rafart Apr 19 '19 at 07:22
  • For the 3 point what i wanted to means is that my "changeTracker" module as no DOM for it self and need another object to work with. The use such moudle would allow the parent object to use its property **hasChanges** to do something (ex: show a text to explain the user that he need to save). ...Anyway since i gave me the direction to follow to understand the concept of Vuejs i mak the solution as resolve. I will then try to achieved the same goal differently with another philosophy. Thanks – Guy Baillon Rafart Apr 19 '19 at 07:32
  • What do you mean by "Vue seems to track all of the stuff in 'data'"? What makes you say or think they "track" everything in data? Knowing that will help me answer your question as best I can. Nothing is "tracked" in the data properties - the only time a data property is tracked is if you watch it using a `watch` property or if it is part of a `computed` property. – Matt Oestreich Apr 19 '19 at 15:56
  • For the "changeTracker" module - you could simply write a JavaScript module (could do anything you want) and then import it inside of your component, and use it's functions/constants/classes/etc.. If you want to keep things organized, you could write "changeTracker" as a `mixin` - this provides you access to all of the Vue instance properties/attributes (whatever you want to call them), without tying them to a specific part of the DOM (aka it has no DOM for itself, but you can use the methods/data/computed/watch in any component you want).. You just "mix" it in. – Matt Oestreich Apr 19 '19 at 15:59
  • @GuyBaillonRafart - I have updated my answer with an example of how to use a `mixin` - hope this helps! – Matt Oestreich Apr 19 '19 at 16:06