Angularjs: 'controller as syntax' และ $ watch


153

วิธีสมัครรับข้อมูลการเปลี่ยนแปลงคุณสมบัติเมื่อใช้controller asไวยากรณ์

controller('TestCtrl', function ($scope) {
  this.name = 'Max';
  this.changeName = function () {
    this.name = new Date();
  }
  // not working       
  $scope.$watch("name",function(value){
    console.log(value)
  });
});
<div ng-controller="TestCtrl as test">
  <input type="text" ng-model="test.name" />
  <a ng-click="test.changeName()" href="#">Change Name</a>
</div>  

แล้วสิ่งนี้ $ watch ()? ถูกต้อง: นี่ $ watch ('name', ... )
Joao Polo

คำตอบ:


160

เพียงผูกบริบทที่เกี่ยวข้อง

$scope.$watch(angular.bind(this, function () {
  return this.name;
}), function (newVal) {
  console.log('Name changed to ' + newVal);
});

ตัวอย่าง: http://jsbin.com/yinadoce/1/edit

UPDATE:

คำตอบของ Bogdan Gersak นั้นเทียบเท่ากันจริง ๆ ทั้งสองคำตอบลองเชื่อมโยงthisกับบริบทที่เหมาะสม อย่างไรก็ตามฉันพบว่าคำตอบของเขาสะอาดขึ้น

มีที่กล่าวว่าเป็นครั้งแรกและสำคัญที่สุดที่คุณต้องเข้าใจความคิดพื้นฐานที่อยู่เบื้องหลังมัน

อัปเดต 2:

สำหรับผู้ที่ใช้ ES6 โดยใช้arrow functionคุณได้รับฟังก์ชั่นที่มีบริบทที่ถูกต้อง OOTB

$scope.$watch(() => this.name, function (newVal) {
  console.log('Name changed to ' + newVal);
});

ตัวอย่าง


9
เราสามารถใช้โดยไม่มี $ scope เพื่อหลีกเลี่ยงการรวมกันของ $ และ $ scope?
Miron

4
ไม่อย่างที่ฉันรู้ แต่มันก็ใช้ได้ดี $scopeสำหรับคุณคือบริการประเภทหนึ่งที่ให้วิธีการเหล่านี้
Roy Miloh

คุณสามารถอธิบายได้อย่างชัดเจนว่าnameในreturn this.name;ชื่ออ้างอิงถึงตัวควบคุมหรือคุณสมบัติ " name" ที่นี่หรือไม่?
Jannik Jochem

3
@Jannik angular.bindส่งคืนฟังก์ชันพร้อมบริบทที่ล้อมรอบ (หาเรื่อง # 1) ในกรณีของเราเราผูกthisซึ่งเป็นอินสแตนซ์ของตัวควบคุมไปยังฟังก์ชัน (หาเรื่อง # 2) ดังนั้นthis.nameหมายถึงคุณสมบัติnameของอินสแตนซ์ของตัวควบคุม
Roy Miloh

ฉันคิดว่าฉันเพิ่งเข้าใจว่ามันทำงานอย่างไร เมื่อมีการเรียกฟังก์ชันที่ผูกไว้มันจะประเมินค่าที่เฝ้าดูใช่มั้ย
Jannik Jochem

138

ฉันมักจะทำสิ่งนี้:

controller('TestCtrl', function ($scope) {
    var self = this;

    this.name = 'Max';
    this.changeName = function () {
        this.name = new Date();
   }

   $scope.$watch(function () {
       return self.name;
   },function(value){
        console.log(value)
   });
});

3
ฉันยอมรับว่านี่เป็นคำตอบที่ดีที่สุดแม้ว่าฉันจะเพิ่มว่าความสับสนในเรื่องนี้น่าจะเป็นในการส่งผ่านฟังก์ชั่นเป็นอาร์กิวเมนต์แรกใน$scope.$watchและการใช้ฟังก์ชั่นนั้นเพื่อคืนค่าจากการปิด ฉันยังไม่ได้วิ่งข้ามตัวอย่างอื่นของเรื่องนี้ แต่มันได้ผลและดีที่สุด เหตุผลที่ฉันไม่ได้เลือกคำตอบด้านล่าง (เช่น$scope.$watch('test.name', function (value) {});) เป็นเพราะจำเป็นต้องใช้รหัสที่ฉันตั้งชื่อคอนโทรลเลอร์ในเทมเพลตของฉันหรือใน $ stateProvider ของ ui.router และการเปลี่ยนแปลงใด ๆ
Morris Singer

นอกจากนี้ข้อแตกต่างที่สำคัญระหว่างคำตอบนี้และคำตอบที่ยอมรับในปัจจุบัน (ซึ่งใช้angular.bind) คือว่าคุณต้องการผูกthisหรือเพียงแค่เพิ่มการอ้างอิงอื่นไปthisภายในการปิด สิ่งเหล่านี้เทียบเท่ากับการใช้งานและจากประสบการณ์ของฉันการเลือกประเภทนี้มักเป็นการเรียกแบบอัตนัยและเป็นเรื่องของความคิดเห็นที่แข็งแกร่งมาก
นักร้อง Morris

1
สิ่งหนึ่งที่ดีเกี่ยวกับ ES6 คือการกำจัดการทำวิธีแก้ปัญหา 2 ข้อดังกล่าวเพื่อให้ได้ขอบเขต js ที่เหมาะสม $scope.$watch( ()=> { return this.name' }, function(){} ) ลูกศรอ้วนเพื่อช่วยเหลือ
jusopi

1
คุณสามารถทำได้() => this.name
coblr

คุณสามารถใช้งานได้$scope.$watchCollectionและยังคงได้รับoldVal, newValparams หรือไม่?
Kraken

23

คุณสามารถใช้ได้:

   $scope.$watch("test.name",function(value){
        console.log(value)
   });

นี่ใช้งานได้JSFiddleกับตัวอย่างของคุณ


25
ปัญหาเกี่ยวกับวิธีการนี้คือตอนนี้ JS กำลังพึ่งพา HTML บังคับให้ตัวควบคุมถูกผูกไว้กับชื่อเดียวกัน (ในกรณีนี้คือ "test") ทุกหนทุกแห่งเพื่อให้ $ watch ทำงาน จะเป็นเรื่องง่ายมากที่จะแนะนำข้อบกพร่องที่ลึกซึ้ง
jsdw

สิ่งนี้จะทำงานได้อย่างน่าอัศจรรย์หากคุณเขียน Angular 1 เช่น Angular 2 ซึ่งทุกอย่างเป็นคำสั่ง แม้ว่า Object.observe จะน่าทึ่งในขณะนี้
Langdon

13

เช่นเดียวกับการใช้ "test" จาก "TestCtrl เป็นการทดสอบ" ตามที่อธิบายไว้ในคำตอบอื่นคุณสามารถกำหนดขอบเขต "ของคุณ" ด้วยตนเอง:

controller('TestCtrl', function($scope){
    var self = this;
    $scope.self = self;

    self.name = 'max';
    self.changeName = function(){
            self.name = new Date();
        }

    $scope.$watch("self.name",function(value){
            console.log(value)
        });
})

ด้วยวิธีนี้คุณจะไม่เชื่อมโยงกับชื่อที่ระบุใน DOM ("TestCtrl เป็นการทดสอบ") และคุณยังหลีกเลี่ยงความจำเป็นที่จะต้องผูก (สิ่งนี้) กับฟังก์ชัน

... เพื่อใช้กับ html ดั้งเดิมที่ระบุ:

<div ng-controller="TestCtrl as test">
    <input type="text" ng-model="test.name" />
    <a ng-click="test.changeName()" href="#">Change Name</a>
</div>

แค่อยากรู้สิ่งหนึ่งกล่าว $scopeคือเป็นบริการดังนั้นถ้าเราเพิ่ม$scope.self = thisจากนั้นในอีกคอนโทรลเลอร์หนึ่งถ้าเราทำแบบเดียวกันจะเกิดอะไรขึ้นที่นั่น?
Vivek Kumar

12

AngularJs 1.5 รองรับการเริ่มต้น $ ctrl สำหรับโครงสร้าง ControllerAs

$scope.$watch("$ctrl.name", (value) => {
    console.log(value)
});

ไม่ทำงานสำหรับฉันเมื่อใช้ $ watchGroup นี่เป็นข้อ จำกัด ที่ทราบหรือไม่ คุณสามารถแชร์ลิงก์ไปยังฟีเจอร์นี้ได้หรือไม่เพราะฉันไม่พบอะไรเลย
user1852503

@ user1852503 ดูdocs.angularjs.org/guide/componentตารางเปรียบเทียบคำสั่ง Directive / Component และตรวจสอบบันทึก 'controllerAs'
Niels Steenbeek

ฉันเข้าใจแล้ว. คำตอบของคุณคือการทำให้เข้าใจผิดเล็กน้อย ตัวระบุ $ ctrl ไม่สัมพันธ์กับตัวควบคุมเป็นคุณลักษณะ (เช่น $ index ทำใน ng-repeat) มันเพิ่งเกิดขึ้นเป็นชื่อเริ่มต้นสำหรับตัวควบคุมภายในส่วนประกอบ (และคำถามไม่ได้เกี่ยวกับ องค์ประกอบ)
user1852503

@ user1852503 1) $ ctrl สัมพันธ์กับ Controller (Controller as) 2) คำถามเกี่ยวกับส่วนประกอบเนื่องจากกล่าวถึง: "<div ng-controller =" TestCtrl as test ">" 3) คำตอบทั้งหมดในหน้านี้เป็นอย่างเดียวกันกับคำตอบของฉัน 4) เกี่ยวกับเอกสาร $ watchGroup ควรทำงานได้ดีเมื่อใช้ $ ctrl.name เนื่องจากมันใช้ $ watch
Niels Steenbeek

2

คุณสามารถส่งผ่านฟังก์ชั่นเป็นอาร์กิวเมนต์แรกของ $ watch ():

 app.controller('TestCtrl', function ($scope) {
 this.name = 'Max';

// hmmm, a function
 $scope.$watch(function () {}, function (value){ console.log(value) });
 });

ซึ่งหมายความว่าเราสามารถส่งคืนชื่ออ้างอิงนี้ของเรา:

app.controller('TestCtrl', function ($scope) {
    this.name = 'Max';

    // boom
    $scope.$watch(angular.bind(this, function () {
    return this.name; // `this` IS the `this` above!!
    }), function (value) {
      console.log(value);
    });
});

อ่านโพสต์ที่น่าสนใจเกี่ยวกับหัวข้อ controllerAs https://toddmotto.com/digging-into-angulars-controller-as-syntax/



0

การเขียน $ watch ในไวยากรณ์ ES6 นั้นไม่ง่ายอย่างที่ฉันคาดไว้ นี่คือสิ่งที่คุณสามารถทำได้:

// Assuming
// controllerAs: "ctrl"
// or
// ng-controller="MyCtrl as ctrl"
export class MyCtrl {
  constructor ($scope) {
    'ngInject';
    this.foo = 10;
    // Option 1
    $scope.$watch('ctrl.foo', this.watchChanges());
    // Option 2
    $scope.$watch(() => this.foo, this.watchChanges());
  }

  watchChanges() {
    return (newValue, oldValue) => {
      console.log('new', newValue);
    }
  }
}

-1

หมายเหตุ : สิ่งนี้จะไม่ทำงานเมื่อ View และ Controller เชื่อมต่อกันในเส้นทางหรือผ่านวัตถุนิยามแบบ directive สิ่งที่แสดงด้านล่างจะใช้งานได้เฉพาะเมื่อมี "SomeController as SomeCtrl" ใน HTML เช่นเดียวกับ Mark V. ชี้ให้เห็นในความคิดเห็นด้านล่างและเช่นเดียวกับที่เขาบอกว่ามันจะดีกว่าที่จะทำเช่น Bogdan

ฉันใช้: var vm = this;ในการเริ่มต้นของตัวควบคุมเพื่อให้ได้คำว่า "นี่" ออกไปจากทางของฉัน แล้วและนาฬิกาฉันvm.name = 'Max'; return vm.nameฉันใช้ "vm" เหมือนกับ @Bogdan ใช้ "self" var นี้ไม่ว่าจะเป็น "vm" หรือ "self" เป็นสิ่งจำเป็นเนื่องจากคำว่า "this" ใช้กับบริบทที่แตกต่างกันภายในฟังก์ชัน (ดังนั้นการส่งคืนชื่อนี้จะไม่ทำงาน) และใช่คุณต้องฉีด $ scope ในโซลูชัน "controller as" ที่สวยงามเพื่อให้ได้ $ watch ดูคู่มือสไตล์ของ John Papa: https://github.com/johnpapa/angularjs-styleguide#controllers

function SomeController($scope, $log) {
    var vm = this;
    vm.name = 'Max';

    $scope.$watch('vm.name', function(current, original) {
        $log.info('vm.name was %s', original);
        $log.info('vm.name is now %s', current);
    });
}

11
สิ่งนี้ทำงานได้ตราบใดที่คุณมี "SomeController as vm" ใน HTML ของคุณ แม้ว่ามันจะทำให้เข้าใจผิด: "vm.name" ในการแสดงออกของนาฬิกาไม่มีอะไรเกี่ยวข้องกับ "var vm = this;" วิธีเดียวที่ปลอดภัยในการใช้ $ watch ด้วย "controller as" คือการส่งผ่านฟังก์ชันเป็นอาร์กิวเมนต์แรกตามที่ Bogdan แสดงไว้ด้านบน
Mark Visser

-1

นี่คือวิธีที่คุณทำโดยไม่มี $ scope (และ $ watch!) ข้อผิดพลาด 5 อันดับแรก - การดูที่ไม่เหมาะสม

หากคุณใช้ไวยากรณ์ "controller as" จะดีกว่าและสะอาดกว่าเพื่อหลีกเลี่ยงการใช้ $ scope

นี่คือรหัสของฉันในJSFiddle (ฉันใช้บริการเพื่อเก็บชื่อมิฉะนั้นชุด ES5 Object.defineProperty และรับวิธีทำให้เกิดการโทรไม่สิ้นสุด

var app = angular.module('my-module', []);

app.factory('testService', function() {
    var name = 'Max';

    var getName = function() {
        return name;
    }

    var setName = function(val) {
        name = val;
    }

    return {getName:getName, setName:setName};
});

app.controller('TestCtrl', function (testService) {
    var vm = this;

    vm.changeName = function () {
        vm.name = new Date();
    }

    Object.defineProperty(this, "name", {
        enumerable: true,
        configurable: false,
        get: function() {
            return testService.getName();
        },
        set: function (val) {
            testService.setName(val);
            console.log(vm.name);
        }
    }); 
});

ซอไม่ทำงานและสิ่งนี้จะไม่สังเกตคุณสมบัติของวัตถุ
Rootical V.

@RooticalV ซอทำงานแล้ว (ตรวจสอบให้แน่ใจว่าเมื่อคุณเรียกใช้ AngualrJS คุณระบุประเภทโหลดเป็น nowrap-head / nowrap-body
Binu Jasim

ขออภัย แต่ฉันยังไม่สามารถเรียกใช้มันได้เช่นสงสารเพราะวิธีแก้ปัญหาของคุณมีการแทรกซึมอย่างมาก
happyZZR1400

@happy ให้แน่ใจว่าคุณเลือกไลบรารีเป็น Angular 1.4 (ฉันไม่แน่ใจว่า 2.0 จะใช้งานได้) และประเภทการโหลดเป็นแบบไม่มีการตัดและกดเรียกใช้ มันควรจะทำงาน
Binu Jasim
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.