ฉันสะดุดกับคำถามนี้ที่กำลังมองหาสิ่งที่คล้ายกัน แต่ฉันคิดว่ามันสมควรได้รับการอธิบายอย่างละเอียดเกี่ยวกับสิ่งที่เกิดขึ้นรวมถึงการแก้ปัญหาเพิ่มเติมบางอย่าง
เมื่อการแสดงออกเชิงมุมดังกล่าวเป็นหนึ่งที่คุณใช้เป็นปัจจุบันใน HTML, เชิงมุมชุดโดยอัตโนมัติขึ้น$watch
สำหรับ$scope.foo
, และจะปรับปรุง HTML เมื่อใดก็ตามที่$scope.foo
มีการเปลี่ยนแปลง
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
ปัญหาที่ยังไม่ได้พูดในที่นี้คือหนึ่งในสองสิ่งที่ส่งผลกระทบต่อaService.foo
การเปลี่ยนแปลงจะไม่ถูกตรวจสอบ ความเป็นไปได้ทั้งสองนี้คือ:
aService.foo
กำลังได้รับการตั้งค่าให้เป็นอาร์เรย์ใหม่ในแต่ละครั้งทำให้การอ้างอิงนั้นล้าสมัย
aService.foo
กำลังอัปเดตในลักษณะที่$digest
ไม่ได้เรียกใช้รอบในการอัปเดต
ปัญหาที่ 1: การอ้างอิงที่ล้าสมัย
พิจารณาความเป็นไปได้ครั้งแรกโดยสมมติว่า a $digest
กำลังถูกนำไปใช้หากaService.foo
เป็นอาร์เรย์เดียวกันเสมอชุดที่ตั้งโดยอัตโนมัติ$watch
จะตรวจจับการเปลี่ยนแปลงดังที่แสดงในข้อมูลโค้ดด้านล่าง
โซลูชันที่ 1-a: ตรวจสอบให้แน่ใจว่าอาร์เรย์หรือวัตถุนั้นเป็นวัตถุเดียวกันในการปรับปรุงแต่ละครั้ง
angular.module('myApp', [])
.factory('aService', [
'$interval',
function($interval) {
var service = {
foo: []
};
// Create a new array on each update, appending the previous items and
// adding one new item each time
$interval(function() {
if (service.foo.length < 10) {
var newArray = []
Array.prototype.push.apply(newArray, service.foo);
newArray.push(Math.random());
service.foo = newArray;
}
}, 1000);
return service;
}
])
.factory('aService2', [
'$interval',
function($interval) {
var service = {
foo: []
};
// Keep the same array, just add new items on each update
$interval(function() {
if (service.foo.length < 10) {
service.foo.push(Math.random());
}
}, 1000);
return service;
}
])
.controller('FooCtrl', [
'$scope',
'aService',
'aService2',
function FooCtrl($scope, aService, aService2) {
$scope.foo = aService.foo;
$scope.foo2 = aService2.foo;
}
]);
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="FooCtrl">
<h1>Array changes on each update</h1>
<div ng-repeat="item in foo">{{ item }}</div>
<h1>Array is the same on each udpate</h1>
<div ng-repeat="item in foo2">{{ item }}</div>
</div>
</body>
</html>
ในขณะที่คุณสามารถดู ng ซ้ำที่แนบมาเพื่อที่จะaService.foo
ไม่ปรับปรุงเมื่อaService.foo
การเปลี่ยนแปลง แต่ NG-ซ้ำที่แนบมากับไม่aService2.foo
นี่เป็นเพราะการอ้างอิงของเราaService.foo
ล้าสมัย แต่การอ้างอิงของเราaService2.foo
ไม่ใช่ เราสร้างการอ้างอิงไปยังอาร์เรย์เริ่มต้นด้วย$scope.foo = aService.foo;
ซึ่งถูกยกเลิกโดยบริการในการอัปเดตครั้งถัดไปซึ่งหมายถึง$scope.foo
ไม่ได้อ้างอิงอาร์เรย์ที่เราต้องการอีกต่อไป
อย่างไรก็ตามในขณะที่มีหลายวิธีในการตรวจสอบให้แน่ใจว่าการอ้างอิงเริ่มต้นถูกเก็บไว้ในชั้นเชิงบางครั้งอาจจำเป็นต้องเปลี่ยนวัตถุหรืออาร์เรย์ หรือสิ่งที่ถ้าอ้างอิงคุณสมบัติบริการดั้งเดิมเหมือนString
หรือNumber
? ในกรณีเหล่านั้นเราไม่สามารถอ้างอิงได้อย่างง่ายดาย ดังนั้นสิ่งที่สามารถเราจะทำอย่างไร
คำตอบหลายข้อที่ให้ไว้ก่อนหน้านี้ได้ให้วิธีแก้ปัญหาแล้ว อย่างไรก็ตามฉันเองชอบใช้วิธีง่าย ๆ ที่แนะนำโดยจินและthewallweekในความคิดเห็น:
เพียงอ้างอิง aService.foo ในมาร์กอัป html
โซลูชัน 1-b: แนบบริการกับขอบเขตและการอ้างอิง{service}.{property}
ใน HTML
ความหมายเพียงทำสิ่งนี้:
HTML:
<div ng-controller="FooCtrl">
<div ng-repeat="item in aService.foo">{{ item }}</div>
</div>
JS:
function FooCtrl($scope, aService) {
$scope.aService = aService;
}
angular.module('myApp', [])
.factory('aService', [
'$interval',
function($interval) {
var service = {
foo: []
};
// Create a new array on each update, appending the previous items and
// adding one new item each time
$interval(function() {
if (service.foo.length < 10) {
var newArray = []
Array.prototype.push.apply(newArray, service.foo);
newArray.push(Math.random());
service.foo = newArray;
}
}, 1000);
return service;
}
])
.controller('FooCtrl', [
'$scope',
'aService',
function FooCtrl($scope, aService) {
$scope.aService = aService;
}
]);
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js@1.4.7" data-semver="1.4.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="FooCtrl">
<h1>Array changes on each update</h1>
<div ng-repeat="item in aService.foo">{{ item }}</div>
</div>
</body>
</html>
ด้วยวิธีนี้การ$watch
แก้ไขจะดำเนินการaService.foo
ในแต่ละครั้ง$digest
ซึ่งจะได้รับค่าการอัปเดตที่ถูกต้อง
นี่คือสิ่งที่คุณพยายามทำกับวิธีแก้ปัญหาของคุณ แต่ในทางรอบน้อยกว่ามาก คุณเพิ่มที่ไม่จำเป็น$watch
ในการควบคุมที่ชัดเจนทำให้foo
ใน$scope
ทุกครั้งที่มีการเปลี่ยนแปลง คุณไม่จำเป็นต้องมีสิ่งนั้นเพิ่มเติม$watch
เมื่อคุณแนบaService
แทนที่จะaService.foo
เข้ากับ$scope
และผูกไว้อย่างชัดเจนaService.foo
ในมาร์กอัป
ตอนนี้ทุกอย่างดีและดีสมมติว่ามีการใช้$digest
วัฏจักร ในตัวอย่างของฉันด้านบนฉันใช้$interval
บริการของ Angular เพื่ออัปเดตอาร์เรย์ซึ่งเริ่มการ$digest
วนซ้ำอัตโนมัติหลังจากการอัปเดตแต่ละครั้ง แต่ถ้าตัวแปรบริการ (ไม่ว่าด้วยเหตุผลใดก็ตาม) จะไม่ได้รับการอัปเดตภายใน "โลกเชิงมุม" ในคำอื่น ๆ ที่เราdontมี$digest
วงจรถูกเปิดใช้งานโดยอัตโนมัติเมื่อมีการเปลี่ยนแปลงสถานที่ให้บริการหรือไม่
ปัญหาที่ 2: ไม่มี $digest
วิธีแก้ปัญหาหลายอย่างที่นี่จะแก้ปัญหานี้ แต่ฉันเห็นด้วยกับCode Whisperer :
เหตุผลที่เราใช้กรอบอย่างแองกูลาร์คือไม่ทำให้รูปแบบการสังเกตการณ์ของเราเองไม่น่าเบื่อ
ดังนั้นฉันต้องการใช้การaService.foo
อ้างอิงในมาร์กอัพ HTML ดังที่แสดงในตัวอย่างที่สองข้างต้นและไม่ต้องลงทะเบียนการติดต่อกลับเพิ่มเติมภายในคอนโทรลเลอร์
โซลูชันที่ 2: ใช้ตัวตั้งค่าและทะเยอทะยานด้วย $rootScope.$apply()
ฉันรู้สึกประหลาดใจที่ไม่มีใครยังแนะนำให้ใช้การตั้งค่าและทะเยอทะยาน ความสามารถนี้ถูกนำมาใช้ใน ECMAScript5 และได้มีมานานหลายปีแล้ว แน่นอนนั่นหมายความว่าถ้าด้วยเหตุผลใดก็ตามคุณต้องสนับสนุนเบราว์เซอร์ที่เก่าแก่จริงๆแล้ววิธีนี้จะไม่ทำงาน แต่ฉันรู้สึกเหมือนตัวเกตเตอร์และเซทเตอร์ถูกใช้งานจาวาสคริปต์อย่างมากมาย ในกรณีพิเศษนี้พวกเขาอาจมีประโยชน์มาก:
factory('aService', [
'$rootScope',
function($rootScope) {
var realFoo = [];
var service = {
set foo(a) {
realFoo = a;
$rootScope.$apply();
},
get foo() {
return realFoo;
}
};
// ...
}
angular.module('myApp', [])
.factory('aService', [
'$rootScope',
function($rootScope) {
var realFoo = [];
var service = {
set foo(a) {
realFoo = a;
$rootScope.$apply();
},
get foo() {
return realFoo;
}
};
// Create a new array on each update, appending the previous items and
// adding one new item each time
setInterval(function() {
if (service.foo.length < 10) {
var newArray = [];
Array.prototype.push.apply(newArray, service.foo);
newArray.push(Math.random());
service.foo = newArray;
}
}, 1000);
return service;
}
])
.controller('FooCtrl', [
'$scope',
'aService',
function FooCtrl($scope, aService) {
$scope.aService = aService;
}
]);
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="FooCtrl">
<h1>Using a Getter/Setter</h1>
<div ng-repeat="item in aService.foo">{{ item }}</div>
</div>
</body>
</html>
นี่ฉันเพิ่มตัวแปร 'ส่วนตัว' realFoo
ในการทำงานบริการ: สิ่งนี้รับการปรับปรุงและดึงข้อมูลโดยใช้get foo()
และset foo()
ฟังก์ชั่นตามลำดับบนservice
วัตถุ
หมายเหตุการใช้งาน$rootScope.$apply()
ในฟังก์ชั่นการตั้งค่า service.foo
เพื่อให้แน่ใจว่าเชิงมุมจะต้องตระหนักถึงการเปลี่ยนแปลงใด ๆ ถ้าคุณได้รับข้อผิดพลาด 'inprog' ดูหน้าอ้างอิงนี้มีประโยชน์หรือถ้าคุณใช้เชิงมุม> = 1.3 $rootScope.$applyAsync()
คุณสามารถเพียงแค่การใช้งาน
นอกจากนี้ระวังสิ่งนี้หากaService.foo
มีการอัพเดทบ่อยมากเนื่องจากอาจมีผลกระทบต่อประสิทธิภาพอย่างมาก หากประสิทธิภาพการทำงานจะเป็นปัญหาคุณสามารถตั้งค่ารูปแบบการสังเกตการณ์คล้ายกับคำตอบอื่น ๆ ที่นี่โดยใช้ setter